upstream: github.com/mirleft/ocaml-tls
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

test: fill all 60 empty test suites with meaningful tests

monopam (399 tests, 0.26s):
- Pure modules: opam_transform, mono_lock, changes, changes_aggregated,
import, forks, cross_status, doctor, diff, fork_join, verse
- I/O modules: ctx, git_cli, opam_repo, feature, progress helpers

ocaml-claudeio (246 tests, 0.03s):
- Codec roundtrips: model, tool_input, err, permissions, control,
sdk_control, structured_output, content_block, message, incoming
- Builder patterns: options (17 with_* builders), hooks (4 event types)
- Pure helpers: handler dispatch, mcp_server, server_info, response

ocaml-tls (91 new tests, 0.05s):
- RFC 8446 test vectors: TLS 1.3 key schedule (early/handshake/master
secrets, traffic keys, finished keys, HKDF-Expand-Label)
- RFC 5246: TLS 1.2 PRF output length, label sensitivity, finished
computation (12 bytes per Section 7.4.9)
- Ciphersuite params: key/nonce lengths, hash mappings, forward secrecy
- Handshake validation: client_hello construction, server_hello_valid,
extension checks, ALPN negotiation, signature algorithm filtering
- Utils: List_set operations, sub_equal, init_and_last, first_match

+2017 -18
+304 -2
test/test_ciphersuite.ml
··· 1 - let () = () 2 - let suite = ("ciphersuite", []) 1 + open Tls 2 + 3 + (* Test key_length for AEAD ciphers (TLS 1.2 style, iv=Some) *) 4 + let key_length_aead_128_gcm () = 5 + (* RFC 5288 Section 3: AES-128-GCM uses 16-byte key, 4-byte salt/IV, 0 mac *) 6 + let key, iv, mac = 7 + Ciphersuite.key_length (Some ()) (`AEAD Ciphersuite.AES_128_GCM) 8 + in 9 + Alcotest.(check int) "AES-128-GCM key" 16 key; 10 + Alcotest.(check int) "AES-128-GCM iv" 4 iv; 11 + Alcotest.(check int) "AES-128-GCM mac" 0 mac 12 + 13 + let key_length_aead_256_gcm () = 14 + let key, iv, mac = 15 + Ciphersuite.key_length (Some ()) (`AEAD Ciphersuite.AES_256_GCM) 16 + in 17 + Alcotest.(check int) "AES-256-GCM key" 32 key; 18 + Alcotest.(check int) "AES-256-GCM iv" 4 iv; 19 + Alcotest.(check int) "AES-256-GCM mac" 0 mac 20 + 21 + let key_length_aead_128_ccm () = 22 + let key, iv, mac = 23 + Ciphersuite.key_length (Some ()) (`AEAD Ciphersuite.AES_128_CCM) 24 + in 25 + Alcotest.(check int) "AES-128-CCM key" 16 key; 26 + Alcotest.(check int) "AES-128-CCM iv" 4 iv; 27 + Alcotest.(check int) "AES-128-CCM mac" 0 mac 28 + 29 + let key_length_aead_256_ccm () = 30 + let key, iv, mac = 31 + Ciphersuite.key_length (Some ()) (`AEAD Ciphersuite.AES_256_CCM) 32 + in 33 + Alcotest.(check int) "AES-256-CCM key" 32 key; 34 + Alcotest.(check int) "AES-256-CCM iv" 4 iv; 35 + Alcotest.(check int) "AES-256-CCM mac" 0 mac 36 + 37 + let key_length_aead_chacha20 () = 38 + (* RFC 7905: ChaCha20-Poly1305 uses 32-byte key, 12-byte nonce *) 39 + let key, iv, mac = 40 + Ciphersuite.key_length (Some ()) (`AEAD Ciphersuite.CHACHA20_POLY1305) 41 + in 42 + Alcotest.(check int) "CHACHA20 key" 32 key; 43 + Alcotest.(check int) "CHACHA20 iv" 12 iv; 44 + Alcotest.(check int) "CHACHA20 mac" 0 mac 45 + 46 + (* Test key_length for block ciphers *) 47 + let key_length_3des_sha1 () = 48 + let key, iv, mac = 49 + Ciphersuite.key_length (Some ()) 50 + (`Block (Ciphersuite.TRIPLE_DES_EDE_CBC, `SHA1)) 51 + in 52 + Alcotest.(check int) "3DES key" 24 key; 53 + Alcotest.(check int) "3DES iv" 8 iv; 54 + Alcotest.(check int) "SHA1 mac" 20 mac 55 + 56 + let key_length_aes128_sha256 () = 57 + let key, iv, mac = 58 + Ciphersuite.key_length (Some ()) (`Block (Ciphersuite.AES_128_CBC, `SHA256)) 59 + in 60 + Alcotest.(check int) "AES-128 key" 16 key; 61 + Alcotest.(check int) "AES-128 iv" 16 iv; 62 + Alcotest.(check int) "SHA256 mac" 32 mac 63 + 64 + let key_length_aes256_sha1 () = 65 + let key, iv, mac = 66 + Ciphersuite.key_length (Some ()) (`Block (Ciphersuite.AES_256_CBC, `SHA1)) 67 + in 68 + Alcotest.(check int) "AES-256 key" 32 key; 69 + Alcotest.(check int) "AES-256 iv" 16 iv; 70 + Alcotest.(check int) "SHA1 mac" 20 mac 71 + 72 + (* Test key_length with iv=None (TLS 1.0 style) *) 73 + let key_length_no_iv () = 74 + let key, iv, mac = 75 + Ciphersuite.key_length None (`Block (Ciphersuite.AES_128_CBC, `SHA1)) 76 + in 77 + Alcotest.(check int) "AES-128 key" 16 key; 78 + Alcotest.(check int) "no iv" 0 iv; 79 + Alcotest.(check int) "SHA1 mac" 20 mac 80 + 81 + (* Test kn_13: TLS 1.3 key/nonce lengths per RFC 8446 Section 5.3 *) 82 + let kn_13_aes_128_gcm () = 83 + let k, n = Ciphersuite.kn_13 Ciphersuite.AES_128_GCM in 84 + Alcotest.(check int) "key len" 16 k; 85 + Alcotest.(check int) "nonce len" 12 n 86 + 87 + let kn_13_aes_256_gcm () = 88 + let k, n = Ciphersuite.kn_13 Ciphersuite.AES_256_GCM in 89 + Alcotest.(check int) "key len" 32 k; 90 + Alcotest.(check int) "nonce len" 12 n 91 + 92 + let kn_13_chacha20 () = 93 + let k, n = Ciphersuite.kn_13 Ciphersuite.CHACHA20_POLY1305 in 94 + Alcotest.(check int) "key len" 32 k; 95 + Alcotest.(check int) "nonce len" 12 n 96 + 97 + let kn_13_aes_128_ccm () = 98 + let k, n = Ciphersuite.kn_13 Ciphersuite.AES_128_CCM in 99 + Alcotest.(check int) "key len" 16 k; 100 + Alcotest.(check int) "nonce len" 12 n 101 + 102 + let kn_13_aes_256_ccm () = 103 + let k, n = Ciphersuite.kn_13 Ciphersuite.AES_256_CCM in 104 + Alcotest.(check int) "key len" 32 k; 105 + Alcotest.(check int) "nonce len" 12 n 106 + 107 + (* Test hash13: hash algorithm mapping per RFC 8446 Section B.4 *) 108 + let hash13_aes_128_gcm_sha256 () = 109 + Alcotest.(check bool) 110 + "SHA256" true 111 + (Ciphersuite.hash13 `AES_128_GCM_SHA256 = `SHA256) 112 + 113 + let hash13_aes_256_gcm_sha384 () = 114 + Alcotest.(check bool) 115 + "SHA384" true 116 + (Ciphersuite.hash13 `AES_256_GCM_SHA384 = `SHA384) 117 + 118 + let hash13_chacha20_sha256 () = 119 + Alcotest.(check bool) 120 + "SHA256" true 121 + (Ciphersuite.hash13 `CHACHA20_POLY1305_SHA256 = `SHA256) 122 + 123 + let hash13_aes_128_ccm_sha256 () = 124 + Alcotest.(check bool) 125 + "SHA256" true 126 + (Ciphersuite.hash13 `AES_128_CCM_SHA256 = `SHA256) 127 + 128 + (* Test privprot13 *) 129 + let privprot13_mapping () = 130 + Alcotest.(check bool) 131 + "AES_128_GCM" true 132 + (Ciphersuite.privprot13 `AES_128_GCM_SHA256 = Ciphersuite.AES_128_GCM); 133 + Alcotest.(check bool) 134 + "AES_256_GCM" true 135 + (Ciphersuite.privprot13 `AES_256_GCM_SHA384 = Ciphersuite.AES_256_GCM); 136 + Alcotest.(check bool) 137 + "CHACHA20" true 138 + (Ciphersuite.privprot13 `CHACHA20_POLY1305_SHA256 139 + = Ciphersuite.CHACHA20_POLY1305); 140 + Alcotest.(check bool) 141 + "AES_128_CCM" true 142 + (Ciphersuite.privprot13 `AES_128_CCM_SHA256 = Ciphersuite.AES_128_CCM) 143 + 144 + (* Test any_ciphersuite_to_ciphersuite roundtrip *) 145 + let ciphersuite_roundtrip () = 146 + let suites : Ciphersuite.ciphersuite list = 147 + [ 148 + `RSA_WITH_AES_128_CBC_SHA; 149 + `DHE_RSA_WITH_AES_256_GCM_SHA384; 150 + `ECDHE_RSA_WITH_AES_128_GCM_SHA256; 151 + `AES_128_GCM_SHA256; 152 + `AES_256_GCM_SHA384; 153 + `CHACHA20_POLY1305_SHA256; 154 + `ECDHE_ECDSA_WITH_AES_256_GCM_SHA384; 155 + ] 156 + in 157 + List.iter 158 + (fun cs -> 159 + let any = Ciphersuite.ciphersuite_to_any_ciphersuite cs in 160 + match Ciphersuite.any_ciphersuite_to_ciphersuite any with 161 + | Some cs' -> Alcotest.(check bool) "roundtrip" true (cs = cs') 162 + | None -> 163 + Alcotest.failf "roundtrip failed for %a" Ciphersuite.pp_ciphersuite cs) 164 + suites 165 + 166 + (* Test get_keytype_kex_privprot *) 167 + let kex_rsa () = 168 + let _kt, kex, _pp = 169 + Ciphersuite.get_keytype_kex_privprot `RSA_WITH_AES_128_CBC_SHA 170 + in 171 + Alcotest.(check bool) "RSA kex" true (kex = `RSA) 172 + 173 + let kex_dhe () = 174 + let _kt, kex, _pp = 175 + Ciphersuite.get_keytype_kex_privprot `DHE_RSA_WITH_AES_128_GCM_SHA256 176 + in 177 + Alcotest.(check bool) "FFDHE kex" true (kex = `FFDHE) 178 + 179 + let kex_ecdhe () = 180 + let _kt, kex, _pp = 181 + Ciphersuite.get_keytype_kex_privprot `ECDHE_RSA_WITH_AES_128_GCM_SHA256 182 + in 183 + Alcotest.(check bool) "ECDHE kex" true (kex = `ECDHE) 184 + 185 + let kex_ecdhe_ecdsa () = 186 + let kt, kex, _pp = 187 + Ciphersuite.get_keytype_kex_privprot `ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 188 + in 189 + Alcotest.(check bool) "ECDHE kex" true (kex = `ECDHE); 190 + Alcotest.(check bool) "EC keytype" true (kt = `EC) 191 + 192 + (* Test forward secrecy classification *) 193 + let ciphersuite_fs_true () = 194 + Alcotest.(check bool) 195 + "DHE has FS" true 196 + (Ciphersuite.ciphersuite_fs `DHE_RSA_WITH_AES_128_GCM_SHA256); 197 + Alcotest.(check bool) 198 + "ECDHE has FS" true 199 + (Ciphersuite.ciphersuite_fs `ECDHE_RSA_WITH_AES_128_GCM_SHA256) 200 + 201 + let ciphersuite_fs_false () = 202 + Alcotest.(check bool) 203 + "RSA no FS" false 204 + (Ciphersuite.ciphersuite_fs `RSA_WITH_AES_128_CBC_SHA) 205 + 206 + (* Test TLS 1.2 only classification *) 207 + let ciphersuite_tls12_only_true () = 208 + Alcotest.(check bool) 209 + "GCM is 1.2 only" true 210 + (Ciphersuite.ciphersuite_tls12_only `RSA_WITH_AES_128_GCM_SHA256); 211 + Alcotest.(check bool) 212 + "SHA256 CBC is 1.2 only" true 213 + (Ciphersuite.ciphersuite_tls12_only `RSA_WITH_AES_256_CBC_SHA256) 214 + 215 + let ciphersuite_tls12_only_false () = 216 + Alcotest.(check bool) 217 + "3DES SHA1 not 1.2 only" false 218 + (Ciphersuite.ciphersuite_tls12_only `RSA_WITH_3DES_EDE_CBC_SHA) 219 + 220 + (* Test TLS 1.3 classification *) 221 + let ciphersuite_tls13_true () = 222 + Alcotest.(check bool) 223 + "AES_128_GCM_SHA256 is 1.3" true 224 + (Ciphersuite.ciphersuite_tls13 `AES_128_GCM_SHA256); 225 + Alcotest.(check bool) 226 + "AES_256_GCM_SHA384 is 1.3" true 227 + (Ciphersuite.ciphersuite_tls13 `AES_256_GCM_SHA384) 228 + 229 + let ciphersuite_tls13_false () = 230 + Alcotest.(check bool) 231 + "RSA AES not 1.3" false 232 + (Ciphersuite.ciphersuite_tls13 `RSA_WITH_AES_128_CBC_SHA) 233 + 234 + (* Test required_usage *) 235 + let required_usage_rsa () = 236 + Alcotest.(check bool) 237 + "RSA requires key encipherment" true 238 + (Ciphersuite.required_usage `RSA = `Key_encipherment) 239 + 240 + let required_usage_dhe () = 241 + Alcotest.(check bool) 242 + "DHE requires digital signature" true 243 + (Ciphersuite.required_usage `FFDHE = `Digital_signature); 244 + Alcotest.(check bool) 245 + "ECDHE requires digital signature" true 246 + (Ciphersuite.required_usage `ECDHE = `Digital_signature) 247 + 248 + (* Test any_ciphersuite_to_ciphersuite13 *) 249 + let any_to_cs13_valid () = 250 + Alcotest.(check bool) 251 + "TLS_AES_128_GCM maps" true 252 + (Ciphersuite.any_ciphersuite_to_ciphersuite13 Packet.TLS_AES_128_GCM_SHA256 253 + = Some `AES_128_GCM_SHA256); 254 + Alcotest.(check bool) 255 + "TLS_AES_256_GCM maps" true 256 + (Ciphersuite.any_ciphersuite_to_ciphersuite13 Packet.TLS_AES_256_GCM_SHA384 257 + = Some `AES_256_GCM_SHA384) 258 + 259 + let any_to_cs13_invalid () = 260 + Alcotest.(check bool) 261 + "non-1.3 cipher returns None" true 262 + (Ciphersuite.any_ciphersuite_to_ciphersuite13 263 + Packet.TLS_RSA_WITH_AES_128_CBC_SHA 264 + = None) 265 + 266 + let tests = 267 + [ 268 + ("key_length AEAD AES-128-GCM", `Quick, key_length_aead_128_gcm); 269 + ("key_length AEAD AES-256-GCM", `Quick, key_length_aead_256_gcm); 270 + ("key_length AEAD AES-128-CCM", `Quick, key_length_aead_128_ccm); 271 + ("key_length AEAD AES-256-CCM", `Quick, key_length_aead_256_ccm); 272 + ("key_length AEAD CHACHA20", `Quick, key_length_aead_chacha20); 273 + ("key_length 3DES SHA1", `Quick, key_length_3des_sha1); 274 + ("key_length AES-128 SHA256", `Quick, key_length_aes128_sha256); 275 + ("key_length AES-256 SHA1", `Quick, key_length_aes256_sha1); 276 + ("key_length no iv", `Quick, key_length_no_iv); 277 + ("kn_13 AES-128-GCM", `Quick, kn_13_aes_128_gcm); 278 + ("kn_13 AES-256-GCM", `Quick, kn_13_aes_256_gcm); 279 + ("kn_13 CHACHA20", `Quick, kn_13_chacha20); 280 + ("kn_13 AES-128-CCM", `Quick, kn_13_aes_128_ccm); 281 + ("kn_13 AES-256-CCM", `Quick, kn_13_aes_256_ccm); 282 + ("hash13 AES-128-GCM", `Quick, hash13_aes_128_gcm_sha256); 283 + ("hash13 AES-256-GCM", `Quick, hash13_aes_256_gcm_sha384); 284 + ("hash13 CHACHA20", `Quick, hash13_chacha20_sha256); 285 + ("hash13 AES-128-CCM", `Quick, hash13_aes_128_ccm_sha256); 286 + ("privprot13 mapping", `Quick, privprot13_mapping); 287 + ("ciphersuite roundtrip", `Quick, ciphersuite_roundtrip); 288 + ("kex RSA", `Quick, kex_rsa); 289 + ("kex DHE", `Quick, kex_dhe); 290 + ("kex ECDHE", `Quick, kex_ecdhe); 291 + ("kex ECDHE-ECDSA", `Quick, kex_ecdhe_ecdsa); 292 + ("FS true", `Quick, ciphersuite_fs_true); 293 + ("FS false", `Quick, ciphersuite_fs_false); 294 + ("TLS 1.2 only true", `Quick, ciphersuite_tls12_only_true); 295 + ("TLS 1.2 only false", `Quick, ciphersuite_tls12_only_false); 296 + ("TLS 1.3 true", `Quick, ciphersuite_tls13_true); 297 + ("TLS 1.3 false", `Quick, ciphersuite_tls13_false); 298 + ("required_usage RSA", `Quick, required_usage_rsa); 299 + ("required_usage DHE", `Quick, required_usage_dhe); 300 + ("any_to_cs13 valid", `Quick, any_to_cs13_valid); 301 + ("any_to_cs13 invalid", `Quick, any_to_cs13_invalid); 302 + ] 303 + 304 + let suite = ("ciphersuite", tests)
+165 -2
test/test_handshake_client.ml
··· 1 - let () = () 2 - let suite = ("handshake_client", []) 1 + open Tls 2 + 3 + (* Test default_client_hello generation *) 4 + 5 + let null_auth ?ip:_ ~host:_ _certs = Ok None 6 + 7 + let make_client_config ?(version = (`TLS_1_2, `TLS_1_2)) 8 + ?(ciphers = Config.Ciphers.supported) ?alpn_protocols () = 9 + match 10 + Config.client ~authenticator:null_auth ~version ?ciphers:(Some ciphers) 11 + ?alpn_protocols () 12 + with 13 + | Ok c -> Config.of_client c 14 + | Error (`Msg m) -> Alcotest.failf "config error: %s" m 15 + 16 + (* Test that default_client_hello produces a valid client hello *) 17 + let default_ch_has_random () = 18 + let config = make_client_config () in 19 + let ch, _ver, _secrets = Handshake_client.default_client_hello config in 20 + Alcotest.(check int) 21 + "random is 32 bytes" 32 22 + (String.length ch.Core.client_random) 23 + 24 + let default_ch_version () = 25 + let config = make_client_config ~version:(`TLS_1_2, `TLS_1_2) () in 26 + let ch, ver, _secrets = Handshake_client.default_client_hello config in 27 + Alcotest.(check bool) "version is TLS 1.2" true (ver = `TLS_1_2); 28 + Alcotest.(check bool) 29 + "client_version is TLS 1.2" true 30 + (ch.Core.client_version = (`TLS_1_2 :> Core.tls_any_version)) 31 + 32 + let default_ch_has_ciphersuites () = 33 + let config = make_client_config () in 34 + let ch, _ver, _secrets = Handshake_client.default_client_hello config in 35 + Alcotest.(check bool) 36 + "has ciphersuites" true 37 + (List.length ch.Core.ciphersuites > 0) 38 + 39 + let default_ch_ciphersuites_match_config () = 40 + let ciphers = 41 + [ 42 + `RSA_WITH_AES_128_CBC_SHA; 43 + `RSA_WITH_AES_256_CBC_SHA; 44 + `DHE_RSA_WITH_AES_128_CBC_SHA; 45 + ] 46 + in 47 + let config = make_client_config ~ciphers () in 48 + let ch, _ver, _secrets = Handshake_client.default_client_hello config in 49 + let expected = List.map Ciphersuite.ciphersuite_to_any_ciphersuite ciphers in 50 + (* The CH ciphersuites should contain all configured ones *) 51 + List.iter 52 + (fun cs -> 53 + Alcotest.(check bool) 54 + "cipher present" true 55 + (List.mem cs ch.Core.ciphersuites)) 56 + expected 57 + 58 + (* Test that ExtendedMasterSecret is always included *) 59 + let default_ch_has_extended_ms () = 60 + let config = make_client_config () in 61 + let ch, _ver, _secrets = Handshake_client.default_client_hello config in 62 + let has_ems = 63 + List.exists 64 + (function `ExtendedMasterSecret -> true | _ -> false) 65 + ch.Core.extensions 66 + in 67 + Alcotest.(check bool) "has ExtendedMasterSecret" true has_ems 68 + 69 + (* Test TLS 1.2 includes signature algorithms *) 70 + let default_ch_tls12_has_sig_algs () = 71 + let config = make_client_config ~version:(`TLS_1_2, `TLS_1_2) () in 72 + let ch, _ver, _secrets = Handshake_client.default_client_hello config in 73 + let has_sigalgs = 74 + List.exists 75 + (function `SignatureAlgorithms _ -> true | _ -> false) 76 + ch.Core.extensions 77 + in 78 + Alcotest.(check bool) "has SignatureAlgorithms" true has_sigalgs 79 + 80 + (* Test TLS 1.0 does not include signature algorithms *) 81 + let default_ch_tls10_no_sig_algs () = 82 + let ciphers = [ `RSA_WITH_AES_128_CBC_SHA; `RSA_WITH_3DES_EDE_CBC_SHA ] in 83 + let config = make_client_config ~version:(`TLS_1_0, `TLS_1_0) ~ciphers () in 84 + let ch, _ver, _secrets = Handshake_client.default_client_hello config in 85 + let has_sigalgs = 86 + List.exists 87 + (function `SignatureAlgorithms _ -> true | _ -> false) 88 + ch.Core.extensions 89 + in 90 + Alcotest.(check bool) "no SignatureAlgorithms for TLS 1.0" false has_sigalgs 91 + 92 + (* Test ALPN extension *) 93 + let default_ch_alpn () = 94 + let config = make_client_config ~alpn_protocols:[ "h2"; "http/1.1" ] () in 95 + let ch, _ver, _secrets = Handshake_client.default_client_hello config in 96 + let alpn = 97 + Utils.map_find 98 + ~f:(function `ALPN ps -> Some ps | _ -> None) 99 + ch.Core.extensions 100 + in 101 + match alpn with 102 + | Some ps -> 103 + Alcotest.(check (list string)) "ALPN protocols" [ "h2"; "http/1.1" ] ps 104 + | None -> Alcotest.fail "expected ALPN extension" 105 + 106 + (* Test TLS 1.3 client hello includes required extensions *) 107 + let default_ch_tls13_extensions () = 108 + let config = make_client_config ~version:(`TLS_1_2, `TLS_1_3) () in 109 + let ch, ver, secrets = Handshake_client.default_client_hello config in 110 + Alcotest.(check bool) "version is TLS 1.3" true (ver = `TLS_1_3); 111 + (* Must have KeyShare *) 112 + let has_keyshare = 113 + List.exists (function `KeyShare _ -> true | _ -> false) ch.Core.extensions 114 + in 115 + Alcotest.(check bool) "has KeyShare" true has_keyshare; 116 + (* Must have SupportedVersions *) 117 + let has_sv = 118 + List.exists 119 + (function `SupportedVersions _ -> true | _ -> false) 120 + ch.Core.extensions 121 + in 122 + Alcotest.(check bool) "has SupportedVersions" true has_sv; 123 + (* Must have SupportedGroups *) 124 + let has_groups = 125 + List.exists 126 + (function `SupportedGroups _ -> true | _ -> false) 127 + ch.Core.extensions 128 + in 129 + Alcotest.(check bool) "has SupportedGroups" true has_groups; 130 + (* Must have SignatureAlgorithms *) 131 + let has_sigalgs = 132 + List.exists 133 + (function `SignatureAlgorithms _ -> true | _ -> false) 134 + ch.Core.extensions 135 + in 136 + Alcotest.(check bool) "has SignatureAlgorithms" true has_sigalgs; 137 + (* Secrets should be non-empty for TLS 1.3 *) 138 + Alcotest.(check bool) "has secrets" true (List.length secrets > 0) 139 + 140 + (* Test that each client random is unique (non-deterministic) *) 141 + let default_ch_unique_randoms () = 142 + let config = make_client_config () in 143 + let ch1, _, _ = Handshake_client.default_client_hello config in 144 + let ch2, _, _ = Handshake_client.default_client_hello config in 145 + Alcotest.(check bool) 146 + "different randoms" false 147 + (String.equal ch1.Core.client_random ch2.Core.client_random) 148 + 149 + let tests = 150 + [ 151 + ("CH has 32-byte random", `Quick, default_ch_has_random); 152 + ("CH version matches config", `Quick, default_ch_version); 153 + ("CH has ciphersuites", `Quick, default_ch_has_ciphersuites); 154 + ( "CH ciphersuites match config", 155 + `Quick, 156 + default_ch_ciphersuites_match_config ); 157 + ("CH has ExtendedMasterSecret", `Quick, default_ch_has_extended_ms); 158 + ("CH TLS 1.2 has sig algs", `Quick, default_ch_tls12_has_sig_algs); 159 + ("CH TLS 1.0 no sig algs", `Quick, default_ch_tls10_no_sig_algs); 160 + ("CH ALPN", `Quick, default_ch_alpn); 161 + ("CH TLS 1.3 extensions", `Quick, default_ch_tls13_extensions); 162 + ("CH unique randoms", `Quick, default_ch_unique_randoms); 163 + ] 164 + 165 + let suite = ("handshake_client", tests)
+187 -2
test/test_handshake_client13.ml
··· 1 - let () = () 2 - let suite = ("handshake_client13", []) 1 + open Tls 2 + 3 + (* Tests for TLS 1.3 client handshake. 4 + 5 + The Handshake_client13 module is largely stateful and processes 6 + complete handshake messages. We test the observable properties 7 + of TLS 1.3 client hello construction and key share generation. *) 8 + 9 + let null_auth ?ip:_ ~host:_ _certs = Ok None 10 + 11 + let make_tls13_config ?(groups = Config.supported_groups) ?alpn_protocols () = 12 + match 13 + Config.client ~authenticator:null_auth ~version:(`TLS_1_2, `TLS_1_3) ~groups 14 + ?alpn_protocols () 15 + with 16 + | Ok c -> Config.of_client c 17 + | Error (`Msg m) -> Alcotest.failf "config error: %s" m 18 + 19 + let get_client_extensions (ch : Core.client_hello) = ch.extensions 20 + 21 + (* Test TLS 1.3 client hello has key shares *) 22 + let ch13_has_keyshares () = 23 + let config = make_tls13_config () in 24 + let ch, ver, secrets = Handshake_client.default_client_hello config in 25 + Alcotest.(check bool) "version 1.3" true (ver = `TLS_1_3); 26 + let exts = get_client_extensions ch in 27 + let keyshares = 28 + Utils.map_find ~f:(function `KeyShare ks -> Some ks | _ -> None) exts 29 + in 30 + match keyshares with 31 + | Some ks -> 32 + Alcotest.(check bool) "has keyshares" true (List.length ks > 0); 33 + (* Each keyshare should have a corresponding secret *) 34 + Alcotest.(check bool) 35 + "secrets match keyshares" true 36 + (List.length secrets >= List.length ks) 37 + | None -> Alcotest.fail "expected KeyShare extension" 38 + 39 + (* Test key share groups are from supported groups *) 40 + let ch13_keyshare_groups_valid () = 41 + let groups = [ `X25519; `P256 ] in 42 + let config = make_tls13_config ~groups () in 43 + let ch, _ver, _secrets = Handshake_client.default_client_hello config in 44 + let exts = get_client_extensions ch in 45 + let keyshares = 46 + Utils.map_find ~f:(function `KeyShare ks -> Some ks | _ -> None) exts 47 + in 48 + match keyshares with 49 + | Some ks -> 50 + let named_groups = List.map fst ks in 51 + let valid_groups = List.map Core.group_to_named_group groups in 52 + List.iter 53 + (fun ng -> 54 + Alcotest.(check bool) 55 + "keyshare group in config" true (List.mem ng valid_groups)) 56 + named_groups 57 + | None -> Alcotest.fail "expected KeyShare extension" 58 + 59 + (* Test supported versions extension includes TLS 1.3 *) 60 + let ch13_supported_versions () = 61 + let config = make_tls13_config () in 62 + let ch, _ver, _secrets = Handshake_client.default_client_hello config in 63 + let exts = get_client_extensions ch in 64 + let sv = 65 + Utils.map_find 66 + ~f:(function `SupportedVersions vs -> Some vs | _ -> None) 67 + exts 68 + in 69 + match sv with 70 + | Some vs -> 71 + let has_13 = List.exists (function `TLS_1_3 -> true | _ -> false) vs in 72 + Alcotest.(check bool) "includes TLS 1.3" true has_13 73 + | None -> Alcotest.fail "expected SupportedVersions extension" 74 + 75 + (* Test supported groups extension *) 76 + let ch13_supported_groups () = 77 + let groups = [ `X25519; `P384 ] in 78 + let config = make_tls13_config ~groups () in 79 + let ch, _ver, _secrets = Handshake_client.default_client_hello config in 80 + let exts = get_client_extensions ch in 81 + let sg = 82 + Utils.map_find 83 + ~f:(function `SupportedGroups gs -> Some gs | _ -> None) 84 + exts 85 + in 86 + match sg with 87 + | Some gs -> 88 + let expected = List.map Core.group_to_named_group groups in 89 + List.iter 90 + (fun g -> Alcotest.(check bool) "group present" true (List.mem g gs)) 91 + expected 92 + | None -> Alcotest.fail "expected SupportedGroups extension" 93 + 94 + (* Test key shares have non-empty public keys *) 95 + let ch13_keyshare_nonempty () = 96 + let config = make_tls13_config () in 97 + let ch, _ver, _secrets = Handshake_client.default_client_hello config in 98 + let exts = get_client_extensions ch in 99 + let keyshares = 100 + Utils.map_find ~f:(function `KeyShare ks -> Some ks | _ -> None) exts 101 + in 102 + match keyshares with 103 + | Some ks -> 104 + List.iter 105 + (fun (_g, share) -> 106 + Alcotest.(check bool) "share not empty" true (String.length share > 0)) 107 + ks 108 + | None -> Alcotest.fail "expected KeyShare extension" 109 + 110 + (* Test ALPN is forwarded in TLS 1.3 client hello *) 111 + let ch13_alpn () = 112 + let config = make_tls13_config ~alpn_protocols:[ "h2"; "http/1.1" ] () in 113 + let ch, _ver, _secrets = Handshake_client.default_client_hello config in 114 + let exts = get_client_extensions ch in 115 + let alpn = 116 + Utils.map_find ~f:(function `ALPN ps -> Some ps | _ -> None) exts 117 + in 118 + match alpn with 119 + | Some ps -> 120 + Alcotest.(check (list string)) "ALPN protocols" [ "h2"; "http/1.1" ] ps 121 + | None -> Alcotest.fail "expected ALPN extension" 122 + 123 + (* Test that TLS 1.3 ciphersuites are included *) 124 + let ch13_has_tls13_ciphers () = 125 + let config = make_tls13_config () in 126 + let ch, _ver, _secrets = Handshake_client.default_client_hello config in 127 + let has_13_cipher = 128 + List.exists 129 + (fun cs -> Ciphersuite.any_ciphersuite_to_ciphersuite13 cs <> None) 130 + ch.ciphersuites 131 + in 132 + Alcotest.(check bool) "has TLS 1.3 cipher" true has_13_cipher 133 + 134 + (* Test different key share generations produce different public keys *) 135 + let ch13_unique_keyshares () = 136 + let config = make_tls13_config () in 137 + let ch1, _, _ = Handshake_client.default_client_hello config in 138 + let ch2, _, _ = Handshake_client.default_client_hello config in 139 + let get_ks (ch : Core.client_hello) = 140 + Utils.map_find 141 + ~f:(function `KeyShare ks -> Some ks | _ -> None) 142 + ch.extensions 143 + in 144 + match (get_ks ch1, get_ks ch2) with 145 + | Some ks1, Some ks2 -> 146 + (* At least first key share should differ *) 147 + let s1 = snd (List.hd ks1) in 148 + let s2 = snd (List.hd ks2) in 149 + Alcotest.(check bool) "different key shares" false (String.equal s1 s2) 150 + | _ -> Alcotest.fail "expected key shares" 151 + 152 + (* Test key shares are a subset of supported groups (RFC 8446 4.2.8) *) 153 + let ch13_keyshares_subset_of_groups () = 154 + let config = make_tls13_config () in 155 + let ch, _ver, _secrets = Handshake_client.default_client_hello config in 156 + let exts = get_client_extensions ch in 157 + let keyshares = 158 + Utils.map_find ~f:(function `KeyShare ks -> Some ks | _ -> None) exts 159 + in 160 + let groups = 161 + Utils.map_find 162 + ~f:(function `SupportedGroups gs -> Some gs | _ -> None) 163 + exts 164 + in 165 + match (keyshares, groups) with 166 + | Some ks, Some gs -> 167 + List.iter 168 + (fun (g, _) -> 169 + Alcotest.(check bool) 170 + "keyshare group in supported groups" true (List.mem g gs)) 171 + ks 172 + | _ -> Alcotest.fail "expected both extensions" 173 + 174 + let tests = 175 + [ 176 + ("CH13 has keyshares", `Quick, ch13_has_keyshares); 177 + ("CH13 keyshare groups valid", `Quick, ch13_keyshare_groups_valid); 178 + ("CH13 supported versions", `Quick, ch13_supported_versions); 179 + ("CH13 supported groups", `Quick, ch13_supported_groups); 180 + ("CH13 keyshare nonempty", `Quick, ch13_keyshare_nonempty); 181 + ("CH13 ALPN", `Quick, ch13_alpn); 182 + ("CH13 has TLS 1.3 ciphers", `Quick, ch13_has_tls13_ciphers); 183 + ("CH13 unique keyshares", `Quick, ch13_unique_keyshares); 184 + ("CH13 keyshares subset of groups", `Quick, ch13_keyshares_subset_of_groups); 185 + ] 186 + 187 + let suite = ("handshake_client13", tests)
+223 -2
test/test_handshake_common.ml
··· 1 - let () = () 2 - let suite = ("handshake_common", []) 1 + open Tls 2 + 3 + (* Test supported_protocol_version *) 4 + let supported_version_in_range () = 5 + let range = (`TLS_1_0, `TLS_1_2) in 6 + Alcotest.(check bool) 7 + "1.0 in range" true 8 + (Handshake_common.supported_protocol_version range `TLS_1_0 = Some `TLS_1_0); 9 + Alcotest.(check bool) 10 + "1.1 in range" true 11 + (Handshake_common.supported_protocol_version range `TLS_1_1 = Some `TLS_1_1); 12 + Alcotest.(check bool) 13 + "1.2 in range" true 14 + (Handshake_common.supported_protocol_version range `TLS_1_2 = Some `TLS_1_2) 15 + 16 + let supported_version_below_range () = 17 + let range = (`TLS_1_2, `TLS_1_3) in 18 + Alcotest.(check bool) 19 + "1.0 below range" true 20 + (Handshake_common.supported_protocol_version range `TLS_1_0 = None); 21 + Alcotest.(check bool) 22 + "1.1 below range" true 23 + (Handshake_common.supported_protocol_version range `TLS_1_1 = None) 24 + 25 + let supported_version_above_range () = 26 + let range = (`TLS_1_0, `TLS_1_1) in 27 + Alcotest.(check bool) 28 + "1.2 above range" true 29 + (Handshake_common.supported_protocol_version range `TLS_1_2 = None); 30 + Alcotest.(check bool) 31 + "1.3 above range" true 32 + (Handshake_common.supported_protocol_version range `TLS_1_3 = None) 33 + 34 + let supported_version_exact () = 35 + let range = (`TLS_1_2, `TLS_1_2) in 36 + Alcotest.(check bool) 37 + "exact match" true 38 + (Handshake_common.supported_protocol_version range `TLS_1_2 = Some `TLS_1_2); 39 + Alcotest.(check bool) 40 + "below exact" true 41 + (Handshake_common.supported_protocol_version range `TLS_1_1 = None); 42 + Alcotest.(check bool) 43 + "above exact" true 44 + (Handshake_common.supported_protocol_version range `TLS_1_3 = None) 45 + 46 + (* Test server_exts_subset_of_client *) 47 + let server_exts_empty_always_subset () = 48 + let cexts : Core.client_extension list = [ `ExtendedMasterSecret ] in 49 + Alcotest.(check bool) 50 + "empty server exts" true 51 + (Handshake_common.server_exts_subset_of_client [] cexts) 52 + 53 + let server_exts_subset () = 54 + let cexts : Core.client_extension list = 55 + [ `ExtendedMasterSecret; `SecureRenegotiation "" ] 56 + in 57 + let sexts : Core.server_extension list = [ `ExtendedMasterSecret ] in 58 + Alcotest.(check bool) 59 + "server subset of client" true 60 + (Handshake_common.server_exts_subset_of_client sexts cexts) 61 + 62 + let server_exts_not_subset () = 63 + let cexts : Core.client_extension list = [ `ExtendedMasterSecret ] in 64 + let sexts : Core.server_extension list = [ `ALPN "h2" ] in 65 + Alcotest.(check bool) 66 + "server not subset" false 67 + (Handshake_common.server_exts_subset_of_client sexts cexts) 68 + 69 + (* Test server_hello_valid *) 70 + let server_hello_valid_no_dups () = 71 + let sh : Core.server_hello = 72 + { 73 + server_version = `TLS_1_2; 74 + server_random = String.make 32 '\x00'; 75 + sessionid = None; 76 + ciphersuite = `RSA_WITH_AES_128_CBC_SHA; 77 + extensions = [ `Hostname; `ExtendedMasterSecret ]; 78 + } 79 + in 80 + Alcotest.(check bool) "valid SH" true (Handshake_common.server_hello_valid sh) 81 + 82 + let server_hello_valid_dup_exts () = 83 + let sh : Core.server_hello = 84 + { 85 + server_version = `TLS_1_2; 86 + server_random = String.make 32 '\x00'; 87 + sessionid = None; 88 + ciphersuite = `RSA_WITH_AES_128_CBC_SHA; 89 + extensions = [ `Hostname; `Hostname ]; 90 + } 91 + in 92 + Alcotest.(check bool) 93 + "invalid SH (dup exts)" false 94 + (Handshake_common.server_hello_valid sh) 95 + 96 + (* Test hostname extraction *) 97 + let hostname_present () = 98 + let host = Domain_name.of_string_exn "example.com" |> Domain_name.host_exn in 99 + let ch : Core.client_hello = 100 + { 101 + client_version = `TLS_1_2; 102 + client_random = String.make 32 '\x00'; 103 + sessionid = None; 104 + ciphersuites = []; 105 + extensions = [ `Hostname host ]; 106 + } 107 + in 108 + match Handshake_common.hostname ch with 109 + | Some h -> 110 + Alcotest.(check string) "hostname" "example.com" (Domain_name.to_string h) 111 + | None -> Alcotest.fail "expected hostname" 112 + 113 + let hostname_absent () = 114 + let ch : Core.client_hello = 115 + { 116 + client_version = `TLS_1_2; 117 + client_random = String.make 32 '\x00'; 118 + sessionid = None; 119 + ciphersuites = []; 120 + extensions = []; 121 + } 122 + in 123 + Alcotest.(check bool) "no hostname" true (Handshake_common.hostname ch = None) 124 + 125 + (* Test get_secure_renegotiation *) 126 + let secure_reneg_present () = 127 + let exts : Core.server_extension list = 128 + [ `Hostname; `SecureRenegotiation "data" ] 129 + in 130 + match Handshake_common.get_secure_renegotiation exts with 131 + | Some d -> Alcotest.(check string) "reneg data" "data" d 132 + | None -> Alcotest.fail "expected reneg data" 133 + 134 + let secure_reneg_absent () = 135 + let exts : Core.server_extension list = [ `Hostname ] in 136 + Alcotest.(check bool) 137 + "no reneg" true 138 + (Handshake_common.get_secure_renegotiation exts = None) 139 + 140 + (* Test get_alpn_protocols *) 141 + let alpn_present () = 142 + let ch : Core.client_hello = 143 + { 144 + client_version = `TLS_1_2; 145 + client_random = String.make 32 '\x00'; 146 + sessionid = None; 147 + ciphersuites = []; 148 + extensions = [ `ALPN [ "h2"; "http/1.1" ] ]; 149 + } 150 + in 151 + match Handshake_common.get_alpn_protocols ch with 152 + | Some ps -> Alcotest.(check (list string)) "alpn" [ "h2"; "http/1.1" ] ps 153 + | None -> Alcotest.fail "expected alpn" 154 + 155 + let alpn_absent () = 156 + let ch : Core.client_hello = 157 + { 158 + client_version = `TLS_1_2; 159 + client_random = String.make 32 '\x00'; 160 + sessionid = None; 161 + ciphersuites = []; 162 + extensions = []; 163 + } 164 + in 165 + Alcotest.(check bool) 166 + "no alpn" true 167 + (Handshake_common.get_alpn_protocols ch = None) 168 + 169 + (* Test to_sign_1_3 *) 170 + let to_sign_1_3_no_context () = 171 + let result = Handshake_common.to_sign_1_3 None in 172 + (* 64 spaces + 1 null byte *) 173 + Alcotest.(check int) "length" 65 (String.length result); 174 + (* first 64 bytes are 0x20 *) 175 + for i = 0 to 63 do 176 + Alcotest.(check int) "space" 0x20 (Char.code (String.get result i)) 177 + done; 178 + (* last byte is 0x00 *) 179 + Alcotest.(check int) "null" 0x00 (Char.code (String.get result 64)) 180 + 181 + let to_sign_1_3_with_context () = 182 + let ctx = "TLS 1.3, server CertificateVerify" in 183 + let result = Handshake_common.to_sign_1_3 (Some ctx) in 184 + (* 64 spaces + context string + 1 null byte *) 185 + Alcotest.(check int) 186 + "length" 187 + (64 + String.length ctx + 1) 188 + (String.length result); 189 + (* first 64 bytes are spaces *) 190 + for i = 0 to 63 do 191 + Alcotest.(check int) "space" 0x20 (Char.code (String.get result i)) 192 + done; 193 + (* context string follows *) 194 + Alcotest.(check string) 195 + "context" ctx 196 + (String.sub result 64 (String.length ctx)); 197 + (* last byte is null *) 198 + Alcotest.(check int) 199 + "null" 0x00 200 + (Char.code (String.get result (String.length result - 1))) 201 + 202 + let tests = 203 + [ 204 + ("supported_version in range", `Quick, supported_version_in_range); 205 + ("supported_version below range", `Quick, supported_version_below_range); 206 + ("supported_version above range", `Quick, supported_version_above_range); 207 + ("supported_version exact", `Quick, supported_version_exact); 208 + ("server_exts empty always subset", `Quick, server_exts_empty_always_subset); 209 + ("server_exts subset", `Quick, server_exts_subset); 210 + ("server_exts not subset", `Quick, server_exts_not_subset); 211 + ("server_hello_valid no dups", `Quick, server_hello_valid_no_dups); 212 + ("server_hello_valid dup exts", `Quick, server_hello_valid_dup_exts); 213 + ("hostname present", `Quick, hostname_present); 214 + ("hostname absent", `Quick, hostname_absent); 215 + ("secure reneg present", `Quick, secure_reneg_present); 216 + ("secure reneg absent", `Quick, secure_reneg_absent); 217 + ("alpn present", `Quick, alpn_present); 218 + ("alpn absent", `Quick, alpn_absent); 219 + ("to_sign_1_3 no context", `Quick, to_sign_1_3_no_context); 220 + ("to_sign_1_3 with context", `Quick, to_sign_1_3_with_context); 221 + ] 222 + 223 + let suite = ("handshake_common", tests)
+258 -2
test/test_handshake_crypto.ml
··· 1 - let () = () 2 - let suite = ("handshake_crypto", []) 1 + open Tls 2 + 3 + let cs = 4 + let module M = struct 5 + type t = string 6 + 7 + let pp = Ohex.pp_hexdump () 8 + let equal = String.equal 9 + end in 10 + (module M : Alcotest.TESTABLE with type t = M.t) 11 + 12 + (* RFC 5246 Section 5 defines the TLS PRF for TLS 1.2: 13 + PRF(secret, label, seed) = P_<hash>(secret, label + seed) 14 + where P_hash uses HMAC with the cipher suite's hash. 15 + 16 + We test against known good values by verifying properties and 17 + consistency of the PRF implementation. *) 18 + 19 + (* Test PRF output length: PRF should produce exactly the requested number of bytes *) 20 + let prf_output_length () = 21 + let secret = String.make 48 '\xab' in 22 + let seed = String.make 32 '\xcd' in 23 + List.iter 24 + (fun len -> 25 + let result = 26 + Handshake_crypto.pseudo_random_function `TLS_1_2 27 + `RSA_WITH_AES_128_GCM_SHA256 len secret "test label" seed 28 + in 29 + Alcotest.(check int) 30 + (Printf.sprintf "PRF length %d" len) 31 + len (String.length result)) 32 + [ 0; 1; 12; 16; 32; 48; 64; 128; 256 ] 33 + 34 + (* Test PRF determinism: same inputs must produce same output *) 35 + let prf_deterministic () = 36 + let secret = String.make 48 '\x01' in 37 + let seed = String.make 32 '\x02' in 38 + let r1 = 39 + Handshake_crypto.pseudo_random_function `TLS_1_2 40 + `RSA_WITH_AES_128_GCM_SHA256 32 secret "master secret" seed 41 + in 42 + let r2 = 43 + Handshake_crypto.pseudo_random_function `TLS_1_2 44 + `RSA_WITH_AES_128_GCM_SHA256 32 secret "master secret" seed 45 + in 46 + Alcotest.check cs "deterministic" r1 r2 47 + 48 + (* Test that different labels produce different output *) 49 + let prf_different_labels () = 50 + let secret = String.make 48 '\x01' in 51 + let seed = String.make 32 '\x02' in 52 + let r1 = 53 + Handshake_crypto.pseudo_random_function `TLS_1_2 54 + `RSA_WITH_AES_128_GCM_SHA256 32 secret "client finished" seed 55 + in 56 + let r2 = 57 + Handshake_crypto.pseudo_random_function `TLS_1_2 58 + `RSA_WITH_AES_128_GCM_SHA256 32 secret "server finished" seed 59 + in 60 + Alcotest.(check bool) "different labels differ" false (String.equal r1 r2) 61 + 62 + (* Test that different seeds produce different output *) 63 + let prf_different_seeds () = 64 + let secret = String.make 48 '\x01' in 65 + let seed1 = String.make 32 '\x02' in 66 + let seed2 = String.make 32 '\x03' in 67 + let r1 = 68 + Handshake_crypto.pseudo_random_function `TLS_1_2 69 + `RSA_WITH_AES_128_GCM_SHA256 32 secret "test" seed1 70 + in 71 + let r2 = 72 + Handshake_crypto.pseudo_random_function `TLS_1_2 73 + `RSA_WITH_AES_128_GCM_SHA256 32 secret "test" seed2 74 + in 75 + Alcotest.(check bool) "different seeds differ" false (String.equal r1 r2) 76 + 77 + (* Test PRF prefix property: PRF(n) should be a prefix of PRF(n+k) *) 78 + let prf_prefix_property () = 79 + let secret = String.make 48 '\xab' in 80 + let seed = String.make 32 '\xcd' in 81 + let short = 82 + Handshake_crypto.pseudo_random_function `TLS_1_2 83 + `RSA_WITH_AES_128_GCM_SHA256 32 secret "test" seed 84 + in 85 + let long = 86 + Handshake_crypto.pseudo_random_function `TLS_1_2 87 + `RSA_WITH_AES_128_GCM_SHA256 64 secret "test" seed 88 + in 89 + Alcotest.check cs "prefix" short (String.sub long 0 32) 90 + 91 + (* Test TLS 1.0/1.1 PRF uses MD5+SHA1 split (RFC 2246 Section 5) *) 92 + let prf_tls10_deterministic () = 93 + let secret = String.make 48 '\x01' in 94 + let seed = String.make 32 '\x02' in 95 + let r1 = 96 + Handshake_crypto.pseudo_random_function `TLS_1_0 `RSA_WITH_AES_128_CBC_SHA 97 + 32 secret "master secret" seed 98 + in 99 + let r2 = 100 + Handshake_crypto.pseudo_random_function `TLS_1_0 `RSA_WITH_AES_128_CBC_SHA 101 + 32 secret "master secret" seed 102 + in 103 + Alcotest.check cs "TLS 1.0 deterministic" r1 r2 104 + 105 + let prf_tls10_prefix () = 106 + let secret = String.make 48 '\x01' in 107 + let seed = String.make 32 '\x02' in 108 + let short = 109 + Handshake_crypto.pseudo_random_function `TLS_1_0 `RSA_WITH_AES_128_CBC_SHA 110 + 16 secret "test" seed 111 + in 112 + let long = 113 + Handshake_crypto.pseudo_random_function `TLS_1_0 `RSA_WITH_AES_128_CBC_SHA 114 + 64 secret "test" seed 115 + in 116 + Alcotest.check cs "TLS 1.0 prefix" short (String.sub long 0 16) 117 + 118 + (* Test TLS 1.0 vs 1.2 PRF produce different results (different algorithms) *) 119 + let prf_version_difference () = 120 + let secret = String.make 48 '\x01' in 121 + let seed = String.make 32 '\x02' in 122 + let r10 = 123 + Handshake_crypto.pseudo_random_function `TLS_1_0 `RSA_WITH_AES_128_CBC_SHA 124 + 32 secret "test" seed 125 + in 126 + let r12 = 127 + Handshake_crypto.pseudo_random_function `TLS_1_2 `RSA_WITH_AES_128_CBC_SHA 128 + 32 secret "test" seed 129 + in 130 + Alcotest.(check bool) "different versions differ" false (String.equal r10 r12) 131 + 132 + (* Test TLS 1.2 PRF with SHA384 cipher suite *) 133 + let prf_sha384_cipher () = 134 + let secret = String.make 48 '\xab' in 135 + let seed = String.make 32 '\xcd' in 136 + let r256 = 137 + Handshake_crypto.pseudo_random_function `TLS_1_2 138 + `RSA_WITH_AES_128_GCM_SHA256 32 secret "test" seed 139 + in 140 + let r384 = 141 + Handshake_crypto.pseudo_random_function `TLS_1_2 142 + `RSA_WITH_AES_256_GCM_SHA384 32 secret "test" seed 143 + in 144 + (* SHA256 and SHA384 based PRFs should differ *) 145 + Alcotest.(check bool) "SHA256 vs SHA384 differ" false (String.equal r256 r384) 146 + 147 + (* Test finished computation: 12 bytes output per RFC 5246 Section 7.4.9 *) 148 + let finished_output_length () = 149 + let master = String.make 48 '\x01' in 150 + let result = 151 + Handshake_crypto.finished `TLS_1_2 `RSA_WITH_AES_128_GCM_SHA256 master 152 + "client finished" [ "hello"; "world" ] 153 + in 154 + Alcotest.(check int) "finished is 12 bytes" 12 (String.length result) 155 + 156 + (* Test finished: client and server differ *) 157 + let finished_client_server_differ () = 158 + let master = String.make 48 '\x01' in 159 + let log = [ "handshake_msg_1"; "handshake_msg_2" ] in 160 + let client = 161 + Handshake_crypto.finished `TLS_1_2 `RSA_WITH_AES_128_GCM_SHA256 master 162 + "client finished" log 163 + in 164 + let server = 165 + Handshake_crypto.finished `TLS_1_2 `RSA_WITH_AES_128_GCM_SHA256 master 166 + "server finished" log 167 + in 168 + Alcotest.(check bool) "client != server" false (String.equal client server) 169 + 170 + (* Test finished: deterministic *) 171 + let finished_deterministic () = 172 + let master = String.make 48 '\x01' in 173 + let log = [ "msg" ] in 174 + let r1 = 175 + Handshake_crypto.finished `TLS_1_2 `RSA_WITH_AES_128_GCM_SHA256 master 176 + "client finished" log 177 + in 178 + let r2 = 179 + Handshake_crypto.finished `TLS_1_2 `RSA_WITH_AES_128_GCM_SHA256 master 180 + "client finished" log 181 + in 182 + Alcotest.check cs "deterministic finished" r1 r2 183 + 184 + (* Test finished uses the correct internal hash: 185 + For TLS 1.0, the hash is MD5+SHA1 (36 bytes); 186 + for TLS 1.2 with SHA256 cipher, 32 bytes. 187 + We verify this indirectly via finished output. *) 188 + let finished_tls10_output () = 189 + let master = String.make 48 '\xab' in 190 + let log = [ "msg1"; "msg2" ] in 191 + let result = 192 + Handshake_crypto.finished `TLS_1_0 `RSA_WITH_AES_128_CBC_SHA master 193 + "client finished" log 194 + in 195 + Alcotest.(check int) "TLS 1.0 finished is 12 bytes" 12 (String.length result) 196 + 197 + let finished_tls10_vs_tls12 () = 198 + let master = String.make 48 '\xab' in 199 + let log = [ "msg" ] in 200 + let r10 = 201 + Handshake_crypto.finished `TLS_1_0 `RSA_WITH_AES_128_CBC_SHA master 202 + "client finished" log 203 + in 204 + let r12 = 205 + Handshake_crypto.finished `TLS_1_2 `RSA_WITH_AES_128_CBC_SHA master 206 + "client finished" log 207 + in 208 + (* Different PRF algorithms should produce different finished values *) 209 + Alcotest.(check bool) "TLS 1.0 vs 1.2 differ" false (String.equal r10 r12) 210 + 211 + (* Verify PRF against a manually computed HMAC-SHA256 based P_hash. 212 + For TLS 1.2 with AES_128_GCM_SHA256, PRF uses HMAC-SHA256. 213 + P_SHA256(secret, seed) = HMAC_SHA256(secret, A(1) + seed) 214 + where A(1) = HMAC_SHA256(secret, seed) *) 215 + let prf_tls12_manual_verification () = 216 + let secret = "secret_key_for_prf_test_padding!" in 217 + (* 32 bytes *) 218 + let label = "test label" in 219 + let seed = "seed_data_for_testing_prf_func!" in 220 + (* 31 bytes *) 221 + let labelled_seed = label ^ seed in 222 + (* Compute manually: A(1) = HMAC-SHA256(secret, label+seed) *) 223 + let a1 = 224 + Digestif.SHA256.(to_raw_string (hmac_string ~key:secret labelled_seed)) 225 + in 226 + (* P_SHA256 first block = HMAC-SHA256(secret, A(1) + label+seed) *) 227 + let p1 = 228 + Digestif.SHA256.( 229 + to_raw_string (hmac_string ~key:secret (a1 ^ labelled_seed))) 230 + in 231 + (* PRF output for 32 bytes should equal first 32 bytes of P_SHA256 *) 232 + let prf_result = 233 + Handshake_crypto.pseudo_random_function `TLS_1_2 234 + `RSA_WITH_AES_128_GCM_SHA256 32 secret label seed 235 + in 236 + Alcotest.check cs "PRF matches manual HMAC-SHA256" (String.sub p1 0 32) 237 + prf_result 238 + 239 + let tests = 240 + [ 241 + ("PRF output length", `Quick, prf_output_length); 242 + ("PRF deterministic", `Quick, prf_deterministic); 243 + ("PRF different labels", `Quick, prf_different_labels); 244 + ("PRF different seeds", `Quick, prf_different_seeds); 245 + ("PRF prefix property", `Quick, prf_prefix_property); 246 + ("PRF TLS 1.0 deterministic", `Quick, prf_tls10_deterministic); 247 + ("PRF TLS 1.0 prefix", `Quick, prf_tls10_prefix); 248 + ("PRF version difference", `Quick, prf_version_difference); 249 + ("PRF SHA384 cipher", `Quick, prf_sha384_cipher); 250 + ("finished output length", `Quick, finished_output_length); 251 + ("finished client/server differ", `Quick, finished_client_server_differ); 252 + ("finished deterministic", `Quick, finished_deterministic); 253 + ("finished TLS 1.0 output", `Quick, finished_tls10_output); 254 + ("finished TLS 1.0 vs 1.2", `Quick, finished_tls10_vs_tls12); 255 + ("PRF TLS 1.2 manual verification", `Quick, prf_tls12_manual_verification); 256 + ] 257 + 258 + let suite = ("handshake_crypto", tests)
+256 -2
test/test_handshake_crypto13.ml
··· 1 - let () = () 2 - let suite = ("handshake_crypto13", []) 1 + open Tls 2 + 3 + let cs = 4 + let module M = struct 5 + type t = string 6 + 7 + let pp = Ohex.pp_hexdump () 8 + let equal = String.equal 9 + end in 10 + (module M : Alcotest.TESTABLE with type t = M.t) 11 + 12 + (* RFC 8446 Appendix A / draft-ietf-tls-tls13-vectors-07 test vectors. 13 + These are the same vectors used in test_tls_crypto.ml but we test 14 + the individual Handshake_crypto13 functions in isolation. *) 15 + 16 + let cipher = `AES_128_GCM_SHA256 17 + let hash = Ciphersuite.hash13 cipher 18 + 19 + (* Test derive_secret_no_hash with known values *) 20 + 21 + (* RFC 5869 Appendix A.1: HKDF-Expand test case 1 22 + (We test via the TLS 1.3 HKDF-Expand-Label wrapper) *) 23 + 24 + (* Test empty cipher creates correct initial state *) 25 + let empty_state () = 26 + let t = Handshake_crypto13.empty cipher in 27 + Alcotest.(check int) 28 + "empty secret is empty string" 0 29 + (String.length t.State.secret); 30 + Alcotest.(check bool) "hash is SHA256" true (t.State.hash = `SHA256) 31 + 32 + let empty_state_sha384 () = 33 + let t = Handshake_crypto13.empty `AES_256_GCM_SHA384 in 34 + Alcotest.(check bool) "hash is SHA384" true (t.State.hash = `SHA384) 35 + 36 + (* Test derive: first extraction with zero PSK. 37 + From RFC 8446 key schedule: Early Secret = HKDF-Extract(0, 0) 38 + salt="" (empty), ikm = 0^32 *) 39 + let derive_early_secret () = 40 + let expected = 41 + Ohex.decode 42 + "33ad0a1c607ec03b09e6cd9893680ce210adf300aa1f2660e1b22e10f170f92a" 43 + in 44 + let t = Handshake_crypto13.(derive (empty cipher) (String.make 32 '\x00')) in 45 + Alcotest.check cs "early secret" expected t.State.secret 46 + 47 + (* Test derive_secret_no_hash: derive "derived" from early secret *) 48 + let derive_derived_from_early () = 49 + let early_secret = 50 + Ohex.decode 51 + "33ad0a1c607ec03b09e6cd9893680ce210adf300aa1f2660e1b22e10f170f92a" 52 + in 53 + let expected = 54 + Ohex.decode 55 + "6f2615a108c702c5678f54fc9dbab69716c076189c48250cebeac3576c3611ba" 56 + in 57 + let module H = (val Digestif.module_of_hash' hash) in 58 + let hash_val = H.(to_raw_string (digest_string "")) in 59 + let result = 60 + Handshake_crypto13.derive_secret_no_hash hash early_secret ~ctx:hash_val 61 + "derived" 62 + in 63 + Alcotest.check cs "derived secret" expected result 64 + 65 + (* Test handshake secret extraction *) 66 + let extract_handshake_secret () = 67 + let salt = 68 + Ohex.decode 69 + "6f2615a108c702c5678f54fc9dbab69716c076189c48250cebeac3576c3611ba" 70 + in 71 + let ikm = 72 + Ohex.decode 73 + "8bd4054fb55b9d63fdfbacf9f04b9f0d35e6d63f537563efd46272900f89492d" 74 + in 75 + let expected = 76 + Ohex.decode 77 + "1dc826e93606aa6fdc0aadc12f741b01046aa6b99f691ed221a9f0ca043fbeac" 78 + in 79 + let result = Hkdf.extract ~hash ~salt ikm in 80 + Alcotest.check cs "handshake secret" expected result 81 + 82 + (* Test traffic_key derivation *) 83 + let traffic_key_derivation () = 84 + let c_hs_traffic = 85 + Ohex.decode 86 + "b3eddb126e067f35a780b3abf45e2d8f3b1a950738f52e9600746a0e27a55a21" 87 + in 88 + let expected_key = Ohex.decode "dbfaa693d1762c5b666af5d950258d01" in 89 + let expected_iv = Ohex.decode "5bd3c71b836e0b76bb73265f" in 90 + let key, iv = Handshake_crypto13.traffic_key cipher c_hs_traffic in 91 + Alcotest.check cs "client hs key" expected_key key; 92 + Alcotest.check cs "client hs iv" expected_iv iv 93 + 94 + let traffic_key_server () = 95 + let s_hs_traffic = 96 + Ohex.decode 97 + "b67b7d690cc16c4e75e54213cb2d37b4e9c912bcded9105d42befd59d391ad38" 98 + in 99 + let expected_key = Ohex.decode "3fce516009c21727d0f2e4e86ee403bc" in 100 + let expected_iv = Ohex.decode "5d313eb2671276ee13000b30" in 101 + let key, iv = Handshake_crypto13.traffic_key cipher s_hs_traffic in 102 + Alcotest.check cs "server hs key" expected_key key; 103 + Alcotest.check cs "server hs iv" expected_iv iv 104 + 105 + (* Test derive_secret_no_hash for key and iv expansion *) 106 + let expand_key_from_traffic_secret () = 107 + let c_hs_traffic = 108 + Ohex.decode 109 + "b3eddb126e067f35a780b3abf45e2d8f3b1a950738f52e9600746a0e27a55a21" 110 + in 111 + let expected_key = Ohex.decode "dbfaa693d1762c5b666af5d950258d01" in 112 + let expected_iv = Ohex.decode "5bd3c71b836e0b76bb73265f" in 113 + Alcotest.check cs "key via expand" expected_key 114 + (Handshake_crypto13.derive_secret_no_hash hash c_hs_traffic ~length:16 "key"); 115 + Alcotest.check cs "iv via expand" expected_iv 116 + (Handshake_crypto13.derive_secret_no_hash hash c_hs_traffic ~length:12 "iv") 117 + 118 + (* Test finished key derivation *) 119 + let finished_key_derivation () = 120 + let s_hs_traffic = 121 + Ohex.decode 122 + "b67b7d690cc16c4e75e54213cb2d37b4e9c912bcded9105d42befd59d391ad38" 123 + in 124 + let expected_finished_key = 125 + Ohex.decode 126 + "008d3b66f816ea559f96b537e885c31fc068bf492c652f01f288a1d8cdc19fc8" 127 + in 128 + Alcotest.check cs "finished expanded" expected_finished_key 129 + (Handshake_crypto13.derive_secret_no_hash hash s_hs_traffic "finished") 130 + 131 + (* Test master secret derivation *) 132 + let derive_master_salt () = 133 + let hs_secret = 134 + Ohex.decode 135 + "1dc826e93606aa6fdc0aadc12f741b01046aa6b99f691ed221a9f0ca043fbeac" 136 + in 137 + let expected = 138 + Ohex.decode 139 + "43de77e0c77713859a944db9db2590b53190a65b3ee2e4f12dd7a0bb7ce254b4" 140 + in 141 + let module H = (val Digestif.module_of_hash' hash) in 142 + let hash_val = H.(to_raw_string (digest_string "")) in 143 + Alcotest.check cs "master salt" expected 144 + (Handshake_crypto13.derive_secret_no_hash hash hs_secret ~ctx:hash_val 145 + "derived") 146 + 147 + let extract_master_secret () = 148 + let salt = 149 + Ohex.decode 150 + "43de77e0c77713859a944db9db2590b53190a65b3ee2e4f12dd7a0bb7ce254b4" 151 + in 152 + let expected = 153 + Ohex.decode 154 + "18df06843d13a08bf2a449844c5f8a478001bc4d4c627984d5a41da8d0402919" 155 + in 156 + let result = Hkdf.extract ~hash ~salt (String.make 32 '\x00') in 157 + Alcotest.check cs "master secret" expected result 158 + 159 + (* Test res_secret *) 160 + let res_secret_derivation () = 161 + let res_master = 162 + Ohex.decode 163 + "7df235f2031d2a051287d02b0241b0bfdaf86cc856231f2d5aba46c434ec196c" 164 + in 165 + let nonce = String.make 2 '\x00' in 166 + let expected = 167 + Ohex.decode 168 + "4ecd0eb6ec3b4d87f5d6028f922ca4c5851a277fd41311c9e62d2c9492e1c4f3" 169 + in 170 + Alcotest.check cs "res_secret" expected 171 + (Handshake_crypto13.res_secret hash res_master nonce) 172 + 173 + (* Test full derive chain matches RFC 8446 vectors *) 174 + let full_derive_chain () = 175 + let t0 = Handshake_crypto13.empty cipher in 176 + (* Early secret: extract with zero PSK *) 177 + let t1 = Handshake_crypto13.derive t0 (String.make 32 '\x00') in 178 + let expected_early = 179 + Ohex.decode 180 + "33ad0a1c607ec03b09e6cd9893680ce210adf300aa1f2660e1b22e10f170f92a" 181 + in 182 + Alcotest.check cs "early secret" expected_early t1.State.secret; 183 + (* Handshake secret: derive + extract with ECDHE shared secret *) 184 + let ikm = 185 + Ohex.decode 186 + "8bd4054fb55b9d63fdfbacf9f04b9f0d35e6d63f537563efd46272900f89492d" 187 + in 188 + let t2 = Handshake_crypto13.derive t1 ikm in 189 + let expected_hs = 190 + Ohex.decode 191 + "1dc826e93606aa6fdc0aadc12f741b01046aa6b99f691ed221a9f0ca043fbeac" 192 + in 193 + Alcotest.check cs "handshake secret" expected_hs t2.State.secret; 194 + (* Master secret *) 195 + let t3 = Handshake_crypto13.derive t2 (String.make 32 '\x00') in 196 + let expected_master = 197 + Ohex.decode 198 + "18df06843d13a08bf2a449844c5f8a478001bc4d4c627984d5a41da8d0402919" 199 + in 200 + Alcotest.check cs "master secret" expected_master t3.State.secret 201 + 202 + (* Test derive_secret with log data *) 203 + let derive_c_hs_traffic_from_log () = 204 + let t0 = Handshake_crypto13.empty cipher in 205 + let t1 = Handshake_crypto13.derive t0 (String.make 32 '\x00') in 206 + let ikm = 207 + Ohex.decode 208 + "8bd4054fb55b9d63fdfbacf9f04b9f0d35e6d63f537563efd46272900f89492d" 209 + in 210 + let t2 = Handshake_crypto13.derive t1 ikm in 211 + (* CH + SH from the RFC test vector *) 212 + let ch = 213 + Ohex.decode 214 + "0100 00c0 0303 cb34 ecb1 e781 63ba 1c38c6da cb19 6a6d ffa2 1a8d 9912 \ 215 + ec18 a2ef6283 024d ece7 0000 0613 0113 0313 02010000 9100 0000 0b00 \ 216 + 0900 0006 7365 72766572 ff01 0001 0000 0a00 1400 1200 1d001700 1800 \ 217 + 1901 0001 0101 0201 0301 04002300 0000 3300 2600 2400 1d00 2099 \ 218 + 381de560 e4bd 43d2 3d8e 435a 7dba feb3 c06e51c1 3cae 4d54 1369 1e52 \ 219 + 9aaf 2c00 2b000302 0304 000d 0020 001e 0403 0503 06030203 0804 0805 \ 220 + 0806 0401 0501 0601 02010402 0502 0602 0202 002d 0002 0101 001c0002 \ 221 + 4001" 222 + in 223 + let sh = 224 + Ohex.decode 225 + "0200 0056 0303 a6af 06a4 1218 60dc 5e6e6024 9cd3 4c95 930c 8ac5 cb14 \ 226 + 34da c155772e d3e2 6928 0013 0100 002e 0033 0024001d 0020 c982 8876 \ 227 + 1120 95fe 6676 2bdbf7c6 72e1 56d6 cc25 3b83 3df1 dd69 b1b04e75 1f0f \ 228 + 002b 0002 0304" 229 + in 230 + let log = ch ^ sh in 231 + let c_hs_traffic = Handshake_crypto13.derive_secret t2 "c hs traffic" log in 232 + let expected = 233 + Ohex.decode 234 + "b3eddb126e067f35a780b3abf45e2d8f3b1a950738f52e9600746a0e27a55a21" 235 + in 236 + Alcotest.check cs "c hs traffic" expected c_hs_traffic 237 + 238 + let tests = 239 + [ 240 + ("empty state", `Quick, empty_state); 241 + ("empty state SHA384", `Quick, empty_state_sha384); 242 + ("derive early secret", `Quick, derive_early_secret); 243 + ("derive 'derived' from early", `Quick, derive_derived_from_early); 244 + ("extract handshake secret", `Quick, extract_handshake_secret); 245 + ("traffic key client", `Quick, traffic_key_derivation); 246 + ("traffic key server", `Quick, traffic_key_server); 247 + ("expand key from traffic secret", `Quick, expand_key_from_traffic_secret); 248 + ("finished key derivation", `Quick, finished_key_derivation); 249 + ("derive master salt", `Quick, derive_master_salt); 250 + ("extract master secret", `Quick, extract_master_secret); 251 + ("res_secret derivation", `Quick, res_secret_derivation); 252 + ("full derive chain", `Quick, full_derive_chain); 253 + ("derive c_hs_traffic from log", `Quick, derive_c_hs_traffic_from_log); 254 + ] 255 + 256 + let suite = ("handshake_crypto13", tests)
+210 -2
test/test_handshake_server.ml
··· 1 - let () = () 2 - let suite = ("handshake_server", []) 1 + open Tls 2 + 3 + (* Tests for TLS 1.2 server handshake helper functions. 4 + The server module is mostly stateful, but we can test 5 + the client_hello_valid function and ALPN negotiation 6 + through the handshake_common module. *) 7 + 8 + (* Test client_hello_valid with a proper TLS 1.2 client hello *) 9 + let client_hello_valid_tls12 () = 10 + let ch : Core.client_hello = 11 + { 12 + client_version = `TLS_1_2; 13 + client_random = String.make 32 '\x00'; 14 + sessionid = None; 15 + ciphersuites = [ Packet.TLS_RSA_WITH_AES_128_CBC_SHA ]; 16 + extensions = 17 + [ `SignatureAlgorithms [ `RSA_PKCS1_SHA256 ]; `ExtendedMasterSecret ]; 18 + } 19 + in 20 + match Handshake_common.client_hello_valid `TLS_1_2 ch with 21 + | Ok () -> () 22 + | Error _ -> Alcotest.fail "expected valid client hello" 23 + 24 + (* Test client_hello_valid rejects empty ciphersuites *) 25 + let client_hello_empty_ciphersuites () = 26 + let ch : Core.client_hello = 27 + { 28 + client_version = `TLS_1_2; 29 + client_random = String.make 32 '\x00'; 30 + sessionid = None; 31 + ciphersuites = []; 32 + extensions = []; 33 + } 34 + in 35 + match Handshake_common.client_hello_valid `TLS_1_2 ch with 36 + | Ok () -> Alcotest.fail "expected error for empty ciphersuites" 37 + | Error _ -> () 38 + 39 + (* Test client_hello_valid rejects unsupported cipher-only list *) 40 + let client_hello_no_supported_cipher () = 41 + let ch : Core.client_hello = 42 + { 43 + client_version = `TLS_1_2; 44 + client_random = String.make 32 '\x00'; 45 + sessionid = None; 46 + (* Use FALLBACK_SCSV which is not a real cipher and won't be supported *) 47 + ciphersuites = [ Packet.TLS_FALLBACK_SCSV ]; 48 + extensions = []; 49 + } 50 + in 51 + match Handshake_common.client_hello_valid `TLS_1_2 ch with 52 + | Ok () -> Alcotest.fail "expected error for unsupported ciphersuites" 53 + | Error _ -> () 54 + 55 + (* Test client_hello_valid rejects duplicate extensions *) 56 + let client_hello_dup_extensions () = 57 + let ch : Core.client_hello = 58 + { 59 + client_version = `TLS_1_2; 60 + client_random = String.make 32 '\x00'; 61 + sessionid = None; 62 + ciphersuites = [ Packet.TLS_RSA_WITH_AES_128_CBC_SHA ]; 63 + extensions = 64 + [ 65 + `SignatureAlgorithms [ `RSA_PKCS1_SHA256 ]; 66 + `SignatureAlgorithms [ `RSA_PKCS1_SHA384 ]; 67 + ]; 68 + } 69 + in 70 + match Handshake_common.client_hello_valid `TLS_1_2 ch with 71 + | Ok () -> Alcotest.fail "expected error for duplicate extensions" 72 + | Error _ -> () 73 + 74 + (* Test ALPN protocol negotiation *) 75 + let alpn_negotiation_match () = 76 + let null_auth ?ip:_ ~host:_ _certs = Ok None in 77 + let config = 78 + match 79 + Config.client ~authenticator:null_auth 80 + ~alpn_protocols:[ "h2"; "http/1.1" ] () 81 + with 82 + | Ok c -> Config.of_client c 83 + | Error (`Msg m) -> Alcotest.failf "config error: %s" m 84 + in 85 + let ch : Core.client_hello = 86 + { 87 + client_version = `TLS_1_2; 88 + client_random = String.make 32 '\x00'; 89 + sessionid = None; 90 + ciphersuites = []; 91 + extensions = [ `ALPN [ "http/1.1"; "h2" ] ]; 92 + } 93 + in 94 + match Handshake_common.alpn_protocol config ch with 95 + | Ok (Some proto) -> 96 + (* Server picks first from its own list that client supports *) 97 + Alcotest.(check string) "ALPN negotiated" "h2" proto 98 + | Ok None -> Alcotest.fail "expected ALPN protocol" 99 + | Error _ -> Alcotest.fail "ALPN negotiation error" 100 + 101 + let alpn_negotiation_no_match () = 102 + let null_auth ?ip:_ ~host:_ _certs = Ok None in 103 + let config = 104 + match 105 + Config.client ~authenticator:null_auth ~alpn_protocols:[ "h2" ] () 106 + with 107 + | Ok c -> Config.of_client c 108 + | Error (`Msg m) -> Alcotest.failf "config error: %s" m 109 + in 110 + let ch : Core.client_hello = 111 + { 112 + client_version = `TLS_1_2; 113 + client_random = String.make 32 '\x00'; 114 + sessionid = None; 115 + ciphersuites = []; 116 + extensions = [ `ALPN [ "spdy/3.1" ] ]; 117 + } 118 + in 119 + match Handshake_common.alpn_protocol config ch with 120 + | Ok _ -> Alcotest.fail "expected ALPN failure" 121 + | Error _ -> () 122 + 123 + let alpn_negotiation_none_configured () = 124 + let null_auth ?ip:_ ~host:_ _certs = Ok None in 125 + let config = 126 + match Config.client ~authenticator:null_auth () with 127 + | Ok c -> Config.of_client c 128 + | Error (`Msg m) -> Alcotest.failf "config error: %s" m 129 + in 130 + let ch : Core.client_hello = 131 + { 132 + client_version = `TLS_1_2; 133 + client_random = String.make 32 '\x00'; 134 + sessionid = None; 135 + ciphersuites = []; 136 + extensions = [ `ALPN [ "h2" ] ]; 137 + } 138 + in 139 + match Handshake_common.alpn_protocol config ch with 140 + | Ok None -> () 141 + | Ok (Some _) -> Alcotest.fail "expected no ALPN when not configured" 142 + | Error _ -> Alcotest.fail "unexpected error" 143 + 144 + (* Test empty_session has sensible defaults *) 145 + let empty_session_defaults () = 146 + let s = Handshake_common.empty_session in 147 + Alcotest.(check int) 148 + "empty master secret" 0 149 + (String.length s.common_session_data.master_secret); 150 + Alcotest.(check bool) "no extended ms" false s.extended_ms; 151 + Alcotest.(check bool) 152 + "no peer cert" true 153 + (s.common_session_data.peer_certificate = None); 154 + Alcotest.(check bool) "empty reneg" true (s.renegotiation = ("", "")) 155 + 156 + (* Test client_hello_valid for TLS 1.0 (no sig algs required) *) 157 + let client_hello_valid_tls10 () = 158 + let ch : Core.client_hello = 159 + { 160 + client_version = `TLS_1_0; 161 + client_random = String.make 32 '\x00'; 162 + sessionid = None; 163 + ciphersuites = [ Packet.TLS_RSA_WITH_AES_128_CBC_SHA ]; 164 + extensions = []; 165 + } 166 + in 167 + match Handshake_common.client_hello_valid `TLS_1_0 ch with 168 + | Ok () -> () 169 + | Error _ -> Alcotest.fail "expected valid TLS 1.0 client hello" 170 + 171 + (* Test version negotiation helpers from Core *) 172 + let version_comparison () = 173 + Alcotest.(check int) 174 + "1.0 < 1.1" (-1) 175 + (Core.compare_tls_version `TLS_1_0 `TLS_1_1); 176 + Alcotest.(check int) 177 + "1.2 > 1.1" 1 178 + (Core.compare_tls_version `TLS_1_2 `TLS_1_1); 179 + Alcotest.(check int) 180 + "1.3 = 1.3" 0 181 + (Core.compare_tls_version `TLS_1_3 `TLS_1_3) 182 + 183 + let all_versions_range () = 184 + let vs = Core.all_versions (`TLS_1_0, `TLS_1_3) in 185 + Alcotest.(check int) "4 versions" 4 (List.length vs); 186 + (* all_versions returns in descending order (highest first) *) 187 + Alcotest.(check bool) "starts with 1.3" true (List.hd vs = `TLS_1_3) 188 + 189 + let all_versions_single () = 190 + let vs = Core.all_versions (`TLS_1_2, `TLS_1_2) in 191 + Alcotest.(check int) "1 version" 1 (List.length vs); 192 + Alcotest.(check bool) "is 1.2" true (List.hd vs = `TLS_1_2) 193 + 194 + let tests = 195 + [ 196 + ("CH valid TLS 1.2", `Quick, client_hello_valid_tls12); 197 + ("CH empty ciphersuites", `Quick, client_hello_empty_ciphersuites); 198 + ("CH no supported cipher", `Quick, client_hello_no_supported_cipher); 199 + ("CH duplicate extensions", `Quick, client_hello_dup_extensions); 200 + ("ALPN negotiation match", `Quick, alpn_negotiation_match); 201 + ("ALPN negotiation no match", `Quick, alpn_negotiation_no_match); 202 + ("ALPN none configured", `Quick, alpn_negotiation_none_configured); 203 + ("empty session defaults", `Quick, empty_session_defaults); 204 + ("CH valid TLS 1.0", `Quick, client_hello_valid_tls10); 205 + ("version comparison", `Quick, version_comparison); 206 + ("all_versions range", `Quick, all_versions_range); 207 + ("all_versions single", `Quick, all_versions_single); 208 + ] 209 + 210 + let suite = ("handshake_server", tests)
+257 -2
test/test_handshake_server13.ml
··· 1 - let () = () 2 - let suite = ("handshake_server13", []) 1 + open Tls 2 + 3 + (* Tests for TLS 1.3 server handshake. 4 + 5 + The Handshake_server13 module constructs server hello messages 6 + and manages the TLS 1.3 handshake state. We test the observable 7 + properties of TLS 1.3 validation logic and key schedule. *) 8 + 9 + (* Test client_hello_valid for TLS 1.3 requires specific extensions *) 10 + let ch13_valid_with_all_extensions () = 11 + let ch : Core.client_hello = 12 + { 13 + client_version = `TLS_1_2; 14 + client_random = String.make 32 '\x00'; 15 + sessionid = None; 16 + ciphersuites = [ Packet.TLS_AES_128_GCM_SHA256 ]; 17 + extensions = 18 + [ 19 + `SignatureAlgorithms [ `RSA_PSS_RSAENC_SHA256 ]; 20 + `SupportedGroups [ Packet.X25519 ]; 21 + `KeyShare [ (Packet.X25519, String.make 32 '\x01') ]; 22 + `SupportedVersions [ `TLS_1_3 ]; 23 + ]; 24 + } 25 + in 26 + match Handshake_common.client_hello_valid `TLS_1_3 ch with 27 + | Ok () -> () 28 + | Error _ -> Alcotest.fail "expected valid TLS 1.3 client hello" 29 + 30 + (* Test TLS 1.3 CH requires SignatureAlgorithms *) 31 + let ch13_missing_sig_algs () = 32 + let ch : Core.client_hello = 33 + { 34 + client_version = `TLS_1_2; 35 + client_random = String.make 32 '\x00'; 36 + sessionid = None; 37 + ciphersuites = [ Packet.TLS_AES_128_GCM_SHA256 ]; 38 + extensions = 39 + [ 40 + `SupportedGroups [ Packet.X25519 ]; 41 + `KeyShare [ (Packet.X25519, String.make 32 '\x01') ]; 42 + ]; 43 + } 44 + in 45 + match Handshake_common.client_hello_valid `TLS_1_3 ch with 46 + | Ok () -> Alcotest.fail "expected error for missing sig algs" 47 + | Error _ -> () 48 + 49 + (* Test TLS 1.3 CH requires KeyShare *) 50 + let ch13_missing_keyshare () = 51 + let ch : Core.client_hello = 52 + { 53 + client_version = `TLS_1_2; 54 + client_random = String.make 32 '\x00'; 55 + sessionid = None; 56 + ciphersuites = [ Packet.TLS_AES_128_GCM_SHA256 ]; 57 + extensions = 58 + [ 59 + `SignatureAlgorithms [ `RSA_PSS_RSAENC_SHA256 ]; 60 + `SupportedGroups [ Packet.X25519 ]; 61 + ]; 62 + } 63 + in 64 + match Handshake_common.client_hello_valid `TLS_1_3 ch with 65 + | Ok () -> Alcotest.fail "expected error for missing key share" 66 + | Error _ -> () 67 + 68 + (* Test TLS 1.3 CH requires SupportedGroups *) 69 + let ch13_missing_supported_groups () = 70 + let ch : Core.client_hello = 71 + { 72 + client_version = `TLS_1_2; 73 + client_random = String.make 32 '\x00'; 74 + sessionid = None; 75 + ciphersuites = [ Packet.TLS_AES_128_GCM_SHA256 ]; 76 + extensions = 77 + [ 78 + `SignatureAlgorithms [ `RSA_PSS_RSAENC_SHA256 ]; 79 + `KeyShare [ (Packet.X25519, String.make 32 '\x01') ]; 80 + ]; 81 + } 82 + in 83 + match Handshake_common.client_hello_valid `TLS_1_3 ch with 84 + | Ok () -> Alcotest.fail "expected error for missing supported groups" 85 + | Error _ -> () 86 + 87 + (* Test TLS 1.3 CH requires key share groups to be subset of supported groups *) 88 + let ch13_keyshare_not_subset () = 89 + let ch : Core.client_hello = 90 + { 91 + client_version = `TLS_1_2; 92 + client_random = String.make 32 '\x00'; 93 + sessionid = None; 94 + ciphersuites = [ Packet.TLS_AES_128_GCM_SHA256 ]; 95 + extensions = 96 + [ 97 + `SignatureAlgorithms [ `RSA_PSS_RSAENC_SHA256 ]; 98 + `SupportedGroups [ Packet.SECP256R1 ]; 99 + (* key share for X25519 which is not in supported groups *) 100 + `KeyShare [ (Packet.X25519, String.make 32 '\x01') ]; 101 + ]; 102 + } 103 + in 104 + match Handshake_common.client_hello_valid `TLS_1_3 ch with 105 + | Ok () -> Alcotest.fail "expected error for keyshare not subset of groups" 106 + | Error _ -> () 107 + 108 + (* Test TLS 1.3 CH rejects duplicate groups *) 109 + let ch13_duplicate_groups () = 110 + let ch : Core.client_hello = 111 + { 112 + client_version = `TLS_1_2; 113 + client_random = String.make 32 '\x00'; 114 + sessionid = None; 115 + ciphersuites = [ Packet.TLS_AES_128_GCM_SHA256 ]; 116 + extensions = 117 + [ 118 + `SignatureAlgorithms [ `RSA_PSS_RSAENC_SHA256 ]; 119 + `SupportedGroups [ Packet.X25519; Packet.X25519 ]; 120 + `KeyShare [ (Packet.X25519, String.make 32 '\x01') ]; 121 + ]; 122 + } 123 + in 124 + match Handshake_common.client_hello_valid `TLS_1_3 ch with 125 + | Ok () -> Alcotest.fail "expected error for duplicate groups" 126 + | Error _ -> () 127 + 128 + (* Test TLS 1.3 CH rejects duplicate key shares *) 129 + let ch13_duplicate_keyshares () = 130 + let ch : Core.client_hello = 131 + { 132 + client_version = `TLS_1_2; 133 + client_random = String.make 32 '\x00'; 134 + sessionid = None; 135 + ciphersuites = [ Packet.TLS_AES_128_GCM_SHA256 ]; 136 + extensions = 137 + [ 138 + `SignatureAlgorithms [ `RSA_PSS_RSAENC_SHA256 ]; 139 + `SupportedGroups [ Packet.X25519 ]; 140 + `KeyShare 141 + [ 142 + (Packet.X25519, String.make 32 '\x01'); 143 + (Packet.X25519, String.make 32 '\x02'); 144 + ]; 145 + ]; 146 + } 147 + in 148 + match Handshake_common.client_hello_valid `TLS_1_3 ch with 149 + | Ok () -> Alcotest.fail "expected error for duplicate key shares" 150 + | Error _ -> () 151 + 152 + (* Test empty_session13 has correct defaults *) 153 + let empty_session13_defaults () = 154 + let s = Handshake_common.empty_session13 `AES_128_GCM_SHA256 in 155 + Alcotest.(check bool) 156 + "cipher matches" true 157 + (s.ciphersuite13 = `AES_128_GCM_SHA256); 158 + Alcotest.(check bool) "not resumed" false s.resumed; 159 + Alcotest.(check bool) "state is established" true (s.state = `Established); 160 + Alcotest.(check int) 161 + "empty exporter secret" 0 162 + (String.length s.exporter_master_secret) 163 + 164 + (* Test Core helper functions used in TLS 1.3 *) 165 + let named_group_roundtrip () = 166 + let groups : Core.group list = [ `X25519; `P256; `P384; `P521; `FFDHE2048 ] in 167 + List.iter 168 + (fun g -> 169 + let ng = Core.group_to_named_group g in 170 + match Core.named_group_to_group ng with 171 + | Some g' -> Alcotest.(check bool) "roundtrip" true (g = g') 172 + | None -> Alcotest.failf "roundtrip failed for group") 173 + groups 174 + 175 + (* Test pair_of_tls_version roundtrip *) 176 + let version_pair_roundtrip () = 177 + let versions : Core.tls_version list = 178 + [ `TLS_1_0; `TLS_1_1; `TLS_1_2; `TLS_1_3 ] 179 + in 180 + List.iter 181 + (fun v -> 182 + let pair = Core.pair_of_tls_version v in 183 + match Core.tls_version_of_pair pair with 184 + | Some v' -> Alcotest.(check bool) "roundtrip" true (v = v') 185 + | None -> Alcotest.fail "version pair roundtrip failed") 186 + versions 187 + 188 + (* Test tls13_sigalg filter *) 189 + let tls13_sigalg_filter () = 190 + (* These should be valid TLS 1.3 sig algs *) 191 + Alcotest.(check bool) 192 + "RSA_PSS_SHA256" true 193 + (Core.tls13_sigalg `RSA_PSS_RSAENC_SHA256); 194 + Alcotest.(check bool) 195 + "ECDSA_P256_SHA256" true 196 + (Core.tls13_sigalg `ECDSA_SECP256R1_SHA256); 197 + Alcotest.(check bool) "ED25519" true (Core.tls13_sigalg `ED25519); 198 + (* These should NOT be valid TLS 1.3 sig algs *) 199 + Alcotest.(check bool) 200 + "RSA_PKCS1_SHA256" false 201 + (Core.tls13_sigalg `RSA_PKCS1_SHA256); 202 + Alcotest.(check bool) "RSA_PKCS1_MD5" false (Core.tls13_sigalg `RSA_PKCS1_MD5); 203 + Alcotest.(check bool) 204 + "ECDSA_SECP256R1_SHA1" false 205 + (Core.tls13_sigalg `ECDSA_SECP256R1_SHA1) 206 + 207 + (* Test pk_matches_sa *) 208 + let pk_matches_sa_rsa () = 209 + (* RSA key should match RSA sigalgs *) 210 + let e = Crypto_pk.Z_extra.of_octets_be "\x01\x00\x01" in 211 + let p = Crypto_pk.Z_extra.of_octets_be (String.make 64 '\xff') in 212 + let q = Crypto_pk.Z_extra.of_octets_be (String.make 64 '\xfe') in 213 + match Crypto_pk.Rsa.priv_of_primes ~e ~p ~q with 214 + | Ok key -> 215 + let pub = `RSA (Crypto_pk.Rsa.pub_of_priv key) in 216 + Alcotest.(check bool) 217 + "RSA matches RSA_PKCS1" true 218 + (Core.pk_matches_sa pub `RSA_PKCS1_SHA256); 219 + Alcotest.(check bool) 220 + "RSA matches RSA_PSS" true 221 + (Core.pk_matches_sa pub `RSA_PSS_RSAENC_SHA256); 222 + Alcotest.(check bool) 223 + "RSA does not match ECDSA" false 224 + (Core.pk_matches_sa pub `ECDSA_SECP256R1_SHA256) 225 + | Error _ -> 226 + (* RSA key generation might fail with these dummy values *) 227 + () 228 + 229 + (* Test DH key generation produces valid keypairs *) 230 + let dh_gen_key_x25519 () = 231 + let _secret, share = Handshake_crypto13.dh_gen_key `X25519 in 232 + Alcotest.(check int) "X25519 share is 32 bytes" 32 (String.length share) 233 + 234 + let dh_gen_key_p256 () = 235 + let _secret, share = Handshake_crypto13.dh_gen_key `P256 in 236 + (* P256 uncompressed point: 1 byte prefix + 32 bytes x + 32 bytes y = 65 *) 237 + Alcotest.(check int) "P256 share is 65 bytes" 65 (String.length share) 238 + 239 + let tests = 240 + [ 241 + ("CH13 valid with all extensions", `Quick, ch13_valid_with_all_extensions); 242 + ("CH13 missing sig algs", `Quick, ch13_missing_sig_algs); 243 + ("CH13 missing keyshare", `Quick, ch13_missing_keyshare); 244 + ("CH13 missing supported groups", `Quick, ch13_missing_supported_groups); 245 + ("CH13 keyshare not subset", `Quick, ch13_keyshare_not_subset); 246 + ("CH13 duplicate groups", `Quick, ch13_duplicate_groups); 247 + ("CH13 duplicate keyshares", `Quick, ch13_duplicate_keyshares); 248 + ("empty_session13 defaults", `Quick, empty_session13_defaults); 249 + ("named group roundtrip", `Quick, named_group_roundtrip); 250 + ("version pair roundtrip", `Quick, version_pair_roundtrip); 251 + ("tls13_sigalg filter", `Quick, tls13_sigalg_filter); 252 + ("pk_matches_sa RSA", `Quick, pk_matches_sa_rsa); 253 + ("dh_gen_key X25519", `Quick, dh_gen_key_x25519); 254 + ("dh_gen_key P256", `Quick, dh_gen_key_p256); 255 + ] 256 + 257 + let suite = ("handshake_server13", tests)
+157 -2
test/test_utils.ml
··· 1 - let () = () 2 - let suite = ("utils", []) 1 + open Tls 2 + 3 + (* Test Utils.List_set.subset *) 4 + let subset_empty_is_subset_of_anything () = 5 + Alcotest.(check bool) 6 + "empty subset of empty" true 7 + (Utils.List_set.subset [] []); 8 + Alcotest.(check bool) 9 + "empty subset of nonempty" true 10 + (Utils.List_set.subset [] [ 1; 2; 3 ]) 11 + 12 + let subset_self () = 13 + Alcotest.(check bool) 14 + "self is subset" true 15 + (Utils.List_set.subset [ 1; 2; 3 ] [ 1; 2; 3 ]) 16 + 17 + let subset_proper () = 18 + Alcotest.(check bool) 19 + "proper subset" true 20 + (Utils.List_set.subset [ 1; 3 ] [ 1; 2; 3 ]) 21 + 22 + let subset_not_subset () = 23 + Alcotest.(check bool) 24 + "not a subset" false 25 + (Utils.List_set.subset [ 1; 4 ] [ 1; 2; 3 ]) 26 + 27 + let subset_unordered () = 28 + (* subset should sort internally, so order should not matter *) 29 + Alcotest.(check bool) 30 + "unordered subset" true 31 + (Utils.List_set.subset [ 3; 1 ] [ 2; 1; 3 ]) 32 + 33 + (* Test Utils.List_set.is_proper_set *) 34 + let is_proper_set_empty () = 35 + Alcotest.(check bool) "empty is a set" true (Utils.List_set.is_proper_set []) 36 + 37 + let is_proper_set_singleton () = 38 + Alcotest.(check bool) 39 + "singleton is a set" true 40 + (Utils.List_set.is_proper_set [ 1 ]) 41 + 42 + let is_proper_set_no_duplicates () = 43 + Alcotest.(check bool) 44 + "no duplicates" true 45 + (Utils.List_set.is_proper_set [ 1; 2; 3 ]) 46 + 47 + let is_proper_set_with_duplicates () = 48 + Alcotest.(check bool) 49 + "has duplicates" false 50 + (Utils.List_set.is_proper_set [ 1; 2; 2; 3 ]) 51 + 52 + let is_proper_set_all_same () = 53 + Alcotest.(check bool) 54 + "all same" false 55 + (Utils.List_set.is_proper_set [ 5; 5; 5 ]) 56 + 57 + (* Test Utils.map_find *) 58 + let map_find_found () = 59 + let result = 60 + Utils.map_find ~f:(fun x -> if x > 3 then Some x else None) [ 1; 2; 4; 5 ] 61 + in 62 + Alcotest.(check (option int)) "finds first match" (Some 4) result 63 + 64 + let map_find_not_found () = 65 + let result = 66 + Utils.map_find ~f:(fun x -> if x > 10 then Some x else None) [ 1; 2; 3 ] 67 + in 68 + Alcotest.(check (option int)) "no match" None result 69 + 70 + let map_find_empty () = 71 + let result = Utils.map_find ~f:(fun x -> Some x) [] in 72 + Alcotest.(check (option int)) "empty list" None result 73 + 74 + (* Test Utils.init_and_last *) 75 + let init_and_last_empty () = 76 + Alcotest.(check bool) "empty list" true (Utils.init_and_last [] = None) 77 + 78 + let init_and_last_singleton () = 79 + match Utils.init_and_last [ 1 ] with 80 + | Some (init, last) -> 81 + Alcotest.(check (list int)) "init" [] init; 82 + Alcotest.(check int) "last" 1 last 83 + | None -> Alcotest.fail "expected Some" 84 + 85 + let init_and_last_multiple () = 86 + match Utils.init_and_last [ 1; 2; 3; 4 ] with 87 + | Some (init, last) -> 88 + Alcotest.(check (list int)) "init" [ 1; 2; 3 ] init; 89 + Alcotest.(check int) "last" 4 last 90 + | None -> Alcotest.fail "expected Some" 91 + 92 + (* Test Utils.first_match *) 93 + let first_match_found () = 94 + Alcotest.(check (option int)) 95 + "first match" (Some 2) 96 + (Utils.first_match [ 1; 2; 3 ] [ 4; 2; 5 ]) 97 + 98 + let first_match_not_found () = 99 + Alcotest.(check (option int)) 100 + "no match" None 101 + (Utils.first_match [ 1; 2 ] [ 4; 5; 6 ]) 102 + 103 + let first_match_empty () = 104 + Alcotest.(check (option int)) "empty l2" None (Utils.first_match [ 1 ] []) 105 + 106 + let first_match_returns_from_l2_order () = 107 + (* first_match iterates l2 and checks membership in l1 *) 108 + Alcotest.(check (option int)) 109 + "order from l2" (Some 3) 110 + (Utils.first_match [ 1; 2; 3 ] [ 3; 2; 1 ]) 111 + 112 + (* Test Utils.sub_equal *) 113 + let sub_equal_true () = 114 + Alcotest.(check bool) 115 + "sub_equal match" true 116 + (Utils.sub_equal ~off:2 ~len:3 "llo" "hello world") 117 + 118 + let sub_equal_false () = 119 + Alcotest.(check bool) 120 + "sub_equal no match" false 121 + (Utils.sub_equal ~off:0 ~len:3 "xyz" "hello") 122 + 123 + let sub_equal_at_start () = 124 + Alcotest.(check bool) 125 + "sub_equal at start" true 126 + (Utils.sub_equal ~off:0 ~len:5 "hello" "hello world") 127 + 128 + let tests = 129 + [ 130 + ( "subset: empty is subset of anything", 131 + `Quick, 132 + subset_empty_is_subset_of_anything ); 133 + ("subset: self", `Quick, subset_self); 134 + ("subset: proper", `Quick, subset_proper); 135 + ("subset: not subset", `Quick, subset_not_subset); 136 + ("subset: unordered", `Quick, subset_unordered); 137 + ("is_proper_set: empty", `Quick, is_proper_set_empty); 138 + ("is_proper_set: singleton", `Quick, is_proper_set_singleton); 139 + ("is_proper_set: no duplicates", `Quick, is_proper_set_no_duplicates); 140 + ("is_proper_set: with duplicates", `Quick, is_proper_set_with_duplicates); 141 + ("is_proper_set: all same", `Quick, is_proper_set_all_same); 142 + ("map_find: found", `Quick, map_find_found); 143 + ("map_find: not found", `Quick, map_find_not_found); 144 + ("map_find: empty", `Quick, map_find_empty); 145 + ("init_and_last: empty", `Quick, init_and_last_empty); 146 + ("init_and_last: singleton", `Quick, init_and_last_singleton); 147 + ("init_and_last: multiple", `Quick, init_and_last_multiple); 148 + ("first_match: found", `Quick, first_match_found); 149 + ("first_match: not found", `Quick, first_match_not_found); 150 + ("first_match: empty", `Quick, first_match_empty); 151 + ("first_match: order from l2", `Quick, first_match_returns_from_l2_order); 152 + ("sub_equal: true", `Quick, sub_equal_true); 153 + ("sub_equal: false", `Quick, sub_equal_false); 154 + ("sub_equal: at start", `Quick, sub_equal_at_start); 155 + ] 156 + 157 + let suite = ("utils", tests)