A batteries included HTTP/1.1 client in OCaml

Add Eio clock to HTTP signature validation (RFC 9421)

HTTP Message Signatures now use an explicit Eio clock for time validation
instead of Ptime_clock.now(), making the code testable and consistent with
Eio's capability-passing design.

Time validations now performed:
- Signatures with `expires` in the past are rejected
- Signatures with `created` in the future (beyond 60s clock skew) are rejected
- If `max_age` is specified and `created` is older, signature is rejected

API changes:
- sign/sign_with_digest now require ~clock parameter
- verify/verify_all now require ~clock parameter
- Auth.apply_signature now requires ~clock parameter

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+123 -52
+3 -2
lib/features/auth.ml
··· 404 404 (** Apply HTTP Message Signature to headers given request context. 405 405 This computes and adds the Signature-Input and Signature headers. 406 406 407 + @param clock Eio clock for timestamp generation 407 408 @param method_ The HTTP method 408 409 @param uri The request URI 409 410 @param headers The headers to sign (and add signature to) 410 411 @param auth The authentication configuration (must be [Signature]) 411 412 @return Updated headers with signature, or original headers if not Signature auth *) 412 - let apply_signature ~method_ ~uri ~headers auth = 413 + let apply_signature ~clock ~method_ ~uri ~headers auth = 413 414 match auth with 414 415 | Signature config -> 415 416 let context = Signature.Context.request ~method_ ~uri ~headers in 416 - (match Signature.sign ~config ~context ~headers with 417 + (match Signature.sign ~clock ~config ~context ~headers with 417 418 | Ok signed_headers -> 418 419 Log.debug (fun m -> m "Applied HTTP message signature"); 419 420 signed_headers
+4 -1
lib/features/auth.mli
··· 202 202 Signature authentication, [None] otherwise. *) 203 203 204 204 val apply_signature : 205 + clock:_ Eio.Time.clock -> 205 206 method_:Method.t -> 206 207 uri:Uri.t -> 207 208 headers:Headers.t -> 208 209 t -> 209 210 Headers.t 210 - (** [apply_signature ~method_ ~uri ~headers auth] applies HTTP Message Signature 211 + (** [apply_signature ~clock ~method_ ~uri ~headers auth] applies HTTP Message Signature 211 212 to [headers] if [auth] is Signature authentication. Returns the headers with 212 213 [Signature-Input] and [Signature] headers added. 213 214 214 215 This function computes the signature based on the request context and adds 215 216 the appropriate headers per RFC 9421. 217 + 218 + @param clock Eio clock for timestamp generation in the signature. 216 219 217 220 If [auth] is not Signature authentication, returns [headers] unchanged. 218 221 If signature computation fails, logs an error and returns [headers] unchanged. *)
+43 -12
lib/features/signature.ml
··· 11 11 (* Result monad syntax for cleaner error handling *) 12 12 let ( let* ) = Result.bind 13 13 14 + (* Helper to get current time from an Eio clock as Ptime.t *) 15 + let now_ptime clock = 16 + let now = Eio.Time.now clock in 17 + match Ptime.of_float_s now with 18 + | Some t -> t 19 + | None -> 20 + (* Fallback: should not happen with valid system time *) 21 + Log.warn (fun m -> m "Failed to convert Eio clock time to Ptime, using epoch"); 22 + Ptime.epoch 23 + 14 24 (* ========================================================================= *) 15 25 (* Algorithms *) 16 26 (* ========================================================================= *) ··· 756 766 | `Component_resolution_error s -> "Component resolution error: " ^ s 757 767 | `Crypto_error s -> "Cryptographic error: " ^ s 758 768 759 - let sign ~(config : config) ~(context : Context.t) ~(headers : Headers.t) = 769 + let sign ~clock ~(config : config) ~(context : Context.t) ~(headers : Headers.t) = 760 770 if not (Key.can_sign config.key) then Error `Missing_private_key 761 771 else 762 772 let alg = Key.algorithm config.key |> Option.value ~default:`Ed25519 in 763 773 let params = 764 774 Params.empty 765 - |> (fun p -> if config.include_created then Params.created (Ptime_clock.now ()) p else p) 775 + |> (fun p -> if config.include_created then Params.created (now_ptime clock) p else p) 766 776 |> Option.fold ~none:Fun.id ~some:Params.keyid config.keyid 767 777 |> Option.fold ~none:Fun.id ~some:Params.tag config.tag 768 778 |> Params.alg alg ··· 783 793 |> Headers.set `Signature_input sig_input_header 784 794 |> Headers.set `Signature sig_header) 785 795 786 - let sign_with_digest ~(config : config) ~(context : Context.t) ~(headers : Headers.t) 796 + let sign_with_digest ~clock ~(config : config) ~(context : Context.t) ~(headers : Headers.t) 787 797 ~(body : string) ~(digest_algorithm : Content_digest.algorithm) = 788 798 (* Add Content-Digest header *) 789 799 let headers = Content_digest.add ~algorithm:digest_algorithm ~body headers in ··· 796 806 else config.components @ [Component.content_digest] 797 807 in 798 808 let config = { config with components } in 799 - sign ~config ~context ~headers 809 + sign ~clock ~config ~context ~headers 800 810 801 811 (* ========================================================================= *) 802 812 (* Verification *) ··· 837 847 verified_components : Component.t list; 838 848 } 839 849 840 - let verify ~(key : Key.t) ?label ?max_age ?required_components 850 + let verify ~clock ~(key : Key.t) ?label ?max_age ?required_components 841 851 ~(context : Context.t) ~(headers : Headers.t) () = 842 852 (* Helper to require a value or return an error *) 843 853 let require opt err = match opt with Some x -> Ok x | None -> Error err in 854 + (* Get current time from Eio clock *) 855 + let now = now_ptime clock in 844 856 (* Parse components from SF items *) 845 857 let parse_components items = 846 858 List.filter_map (fun (item, item_params) -> ··· 870 882 | Some m -> Error (`Required_component_missing (Component.to_identifier m)) 871 883 | None -> Ok () 872 884 in 873 - (* Check signature hasn't expired *) 885 + (* Check signature hasn't expired (RFC 9421 Section 2.2.5) *) 874 886 let check_expiration sig_params = 875 887 match sig_params.Params.expires with 876 - | Some exp when Ptime.is_earlier exp ~than:(Ptime_clock.now ()) -> 888 + | Some exp when Ptime.is_earlier exp ~than:now -> 889 + Log.debug (fun m -> m "Signature expired: expires=%a now=%a" 890 + Ptime.pp exp Ptime.pp now); 877 891 Error `Signature_expired 878 892 | _ -> Ok () 879 893 in 880 - (* Check signature isn't too old *) 894 + (* Check signature isn't too old based on created timestamp *) 881 895 let check_max_age sig_params = 882 896 match max_age, sig_params.Params.created with 883 897 | Some age, Some created -> 884 - let now = Ptime_clock.now () in 885 898 (match Ptime.add_span created age with 886 - | Some limit when Ptime.is_earlier limit ~than:now -> Error `Signature_expired 899 + | Some limit when Ptime.is_earlier limit ~than:now -> 900 + Log.debug (fun m -> m "Signature too old: created=%a max_age=%a now=%a" 901 + Ptime.pp created Ptime.Span.pp age Ptime.pp now); 902 + Error `Signature_expired 903 + | _ -> Ok ()) 904 + | _ -> Ok () 905 + in 906 + (* Check signature was created in the past (not in the future) *) 907 + let check_not_future sig_params = 908 + match sig_params.Params.created with 909 + | Some created when Ptime.is_later created ~than:now -> 910 + (* Allow small clock skew of 60 seconds *) 911 + let skew = Ptime.Span.of_int_s 60 in 912 + (match Ptime.add_span now skew with 913 + | Some limit when Ptime.is_later created ~than:limit -> 914 + Log.warn (fun m -> m "Signature created in the future: created=%a now=%a" 915 + Ptime.pp created Ptime.pp now); 916 + Error `Signature_expired (* Reuse error type for future signatures *) 887 917 | _ -> Ok ()) 888 918 | _ -> Ok () 889 919 in ··· 919 949 | None, None -> `Ed25519 920 950 in 921 951 let* () = check_required components in 952 + let* () = check_not_future sig_params in 922 953 let* () = check_expiration sig_params in 923 954 let* () = check_max_age sig_params in 924 955 let* base = Result.map_error (fun e -> `Component_resolution_error e) ··· 933 964 verified_components = components; 934 965 } 935 966 936 - let verify_all ~key_resolver ?max_age ~(context : Context.t) ~(headers : Headers.t) () = 967 + let verify_all ~clock ~key_resolver ?max_age ~(context : Context.t) ~(headers : Headers.t) () = 937 968 match Headers.get `Signature headers, Headers.get `Signature_input headers with 938 969 | None, _ -> Error `Missing_signature_header 939 970 | _, None -> Error `Missing_signature_input_header ··· 957 988 match key_resolver kid with 958 989 | None -> None 959 990 | Some key -> 960 - match verify ~key ~label ?max_age ~context ~headers () with 991 + match verify ~clock ~key ~label ?max_age ~context ~headers () with 961 992 | Ok r -> Some (Ok r) 962 993 | Error e -> Some (Error e) 963 994 ) sig_dict in
+32 -8
lib/features/signature.mli
··· 18 18 {2 Example Usage} 19 19 20 20 {[ 21 + Eio_main.run @@ fun env -> 22 + let clock = Eio.Stdenv.clock env in 23 + 21 24 (* Create a signing configuration *) 22 25 let key = Signature.Key.ed25519 23 26 ~priv:(Base64.decode_exn "private-key-bytes") ··· 29 32 () in 30 33 31 34 (* Sign headers *) 32 - let ctx = Signature.Context.request ~method_:`GET ~uri:(Huri.of_string "https://example.com/") ~headers in 33 - let signed_headers = Signature.sign ~config ~context:ctx ~headers |> Result.get_ok in 35 + let ctx = Signature.Context.request ~method_:`GET ~uri:(Uri.of_string "https://example.com/") ~headers in 36 + let signed_headers = Signature.sign ~clock ~config ~context:ctx ~headers |> Result.get_ok in 37 + 38 + (* Verify signatures *) 39 + let result = Signature.verify ~clock ~key ~context:ctx ~headers:signed_headers () in 40 + ... 34 41 ]} 35 42 36 43 {2 References} ··· 374 381 (** [sign_error_to_string err] returns a human-readable error message. *) 375 382 376 383 val sign : 384 + clock:_ Eio.Time.clock -> 377 385 config:config -> 378 386 context:Context.t -> 379 387 headers:Headers.t -> 380 388 (Headers.t, sign_error) result 381 - (** [sign ~config ~context ~headers] signs the message and returns 382 - headers with [Signature-Input] and [Signature] headers added. *) 389 + (** [sign ~clock ~config ~context ~headers] signs the message and returns 390 + headers with [Signature-Input] and [Signature] headers added. 391 + 392 + @param clock Eio clock used for the [created] timestamp. *) 383 393 384 394 val sign_with_digest : 395 + clock:_ Eio.Time.clock -> 385 396 config:config -> 386 397 context:Context.t -> 387 398 headers:Headers.t -> 388 399 body:string -> 389 400 digest_algorithm:Content_digest.algorithm -> 390 401 (Headers.t, sign_error) result 391 - (** [sign_with_digest ~config ~context ~headers ~body ~digest_algorithm] 402 + (** [sign_with_digest ~clock ~config ~context ~headers ~body ~digest_algorithm] 392 403 computes Content-Digest, adds it to headers, and signs. 393 - The [content-digest] component is automatically added to signed components. *) 404 + The [content-digest] component is automatically added to signed components. 405 + 406 + @param clock Eio clock used for the [created] timestamp. *) 394 407 395 408 (** {1 Verification} *) 396 409 ··· 421 434 (** Successful verification result. *) 422 435 423 436 val verify : 437 + clock:_ Eio.Time.clock -> 424 438 key:Key.t -> 425 439 ?label:string -> 426 440 ?max_age:Ptime.Span.t -> ··· 429 443 headers:Headers.t -> 430 444 unit -> 431 445 (verify_result, verify_error) result 432 - (** [verify ~key ?label ?max_age ?required_components ~context ~headers] 446 + (** [verify ~clock ~key ?label ?max_age ?required_components ~context ~headers] 433 447 verifies a signature on the message. 434 448 449 + Time validation per RFC 9421: 450 + - If [expires] is present and in the past, verification fails 451 + - If [created] is present and in the future (with 60s clock skew allowance), 452 + verification fails 453 + - If [max_age] is specified and [created] is older than [max_age], 454 + verification fails 455 + 456 + @param clock Eio clock used for time validation. 435 457 @param key The verification key. 436 458 @param label Signature label to verify. Default: first signature found. 437 459 @param max_age Maximum age of signature. If [created] is present and older ··· 442 464 @param headers Headers containing [Signature] and [Signature-Input]. *) 443 465 444 466 val verify_all : 467 + clock:_ Eio.Time.clock -> 445 468 key_resolver:(string -> Key.t option) -> 446 469 ?max_age:Ptime.Span.t -> 447 470 context:Context.t -> 448 471 headers:Headers.t -> 449 472 unit -> 450 473 (verify_result list, verify_error) result 451 - (** [verify_all ~key_resolver ?max_age ~context ~headers] verifies all 474 + (** [verify_all ~clock ~key_resolver ?max_age ~context ~headers] verifies all 452 475 signatures on a message. 453 476 477 + @param clock Eio clock used for time validation. 454 478 @param key_resolver Function to resolve keys by [keyid]. Called for 455 479 each signature with its [keyid] parameter. 456 480 @param max_age Maximum age for all signatures. *)
+1 -1
lib/requests.ml
··· 694 694 (* Apply HTTP Message Signature if configured (RFC 9421) *) 695 695 let signed_headers = match auth with 696 696 | Some a when Auth.is_signature a -> 697 - Auth.apply_signature ~method_ ~uri:original_uri ~headers:base_headers a 697 + Auth.apply_signature ~clock:t.clock ~method_ ~uri:original_uri ~headers:base_headers a 698 698 | _ -> base_headers 699 699 in 700 700
+2 -2
test/dune
··· 28 28 29 29 (executable 30 30 (name test_signature) 31 - (libraries requests alcotest fmt fmt.tty logs logs.fmt mirage-crypto-rng.unix mirage-crypto-ec)) 31 + (libraries requests alcotest eio eio_main fmt fmt.tty logs logs.fmt mirage-crypto-rng.unix mirage-crypto-ec)) 32 32 33 33 (executable 34 34 (name test_rfc9421_vectors) 35 - (libraries requests alcotest base64 digestif fmt fmt.tty logs logs.fmt mirage-crypto-rng.unix mirage-crypto-ec)) 35 + (libraries requests alcotest base64 digestif eio eio_main fmt fmt.tty logs logs.fmt mirage-crypto-rng.unix mirage-crypto-ec)) 36 36 37 37 (executable 38 38 (name test_oauth)
+21 -15
test/test_rfc9421_vectors.ml
··· 11 11 module Signature = Requests.Signature 12 12 module Headers = Requests.Headers 13 13 14 + (** Helper to run tests with an Eio clock *) 15 + let with_clock f () = 16 + Eio_main.run @@ fun env -> 17 + let clock = Eio.Stdenv.clock env in 18 + f clock 19 + 14 20 (* ========================================================================= *) 15 21 (* RFC B.1 Test Keys *) 16 22 (* ========================================================================= *) ··· 101 107 let expected_sig = "pxcQw6G3AjtMBQjwo8XzkZf/bws5LelbaMk5rGIGtE8=" in 102 108 Alcotest.(check string) "B.2.5 HMAC signature" expected_sig sig_b64 103 109 104 - let test_b25_hmac_verify () = 110 + let test_b25_hmac_verify clock = 105 111 (* Create verification context and verify the RFC's signature *) 106 112 let key = Signature.Key.symmetric test_shared_secret in 107 113 let headers = ··· 110 116 |> Headers.set `Signature "sig-b25=:pxcQw6G3AjtMBQjwo8XzkZf/bws5LelbaMk5rGIGtE8=:" 111 117 in 112 118 let context = Signature.Context.request ~method_:`POST ~uri:test_request_uri ~headers in 113 - match Signature.verify ~key ~label:"sig-b25" ~context ~headers () with 119 + match Signature.verify ~clock ~key ~label:"sig-b25" ~context ~headers () with 114 120 | Error e -> 115 121 Alcotest.fail ("B.2.5 HMAC verification failed: " ^ Signature.verify_error_to_string e) 116 122 | Ok result -> ··· 160 166 let expected_sig = "wqcAqbmYJ2ji2glfAMaRy4gruYYnx2nEFN2HN6jrnDnQCK1u02Gb04v9EDgwUPiu4A0w6vuQv5lIp5WPpBKRCw==" in 161 167 Alcotest.(check string) "B.2.6 Ed25519 signature" expected_sig sig_b64 162 168 163 - let test_b26_ed25519_verify () = 169 + let test_b26_ed25519_verify clock = 164 170 (* Create verification context and verify the RFC's signature *) 165 171 let key = Signature.Key.ed25519 ~priv:test_key_ed25519_priv ~pub:test_key_ed25519_pub in 166 172 let headers = ··· 169 175 |> Headers.set `Signature "sig-b26=:wqcAqbmYJ2ji2glfAMaRy4gruYYnx2nEFN2HN6jrnDnQCK1u02Gb04v9EDgwUPiu4A0w6vuQv5lIp5WPpBKRCw==:" 170 176 in 171 177 let context = Signature.Context.request ~method_:`POST ~uri:test_request_uri ~headers in 172 - match Signature.verify ~key ~label:"sig-b26" ~context ~headers () with 178 + match Signature.verify ~clock ~key ~label:"sig-b26" ~context ~headers () with 173 179 | Error e -> 174 180 Alcotest.fail ("B.2.6 Ed25519 verification failed: " ^ Signature.verify_error_to_string e) 175 181 | Ok result -> ··· 220 226 let expected_sig = "ZT1kooQsEHpZ0I1IjCqtQppOmIqlJPeo7DHR3SoMn0s5JZ1eRGS0A+vyYP9t/LXlh5QMFFQ6cpLt2m0pmj3NDA==" in 221 227 Alcotest.(check string) "B.4 Transform Ed25519 signature" expected_sig sig_b64 222 228 223 - let test_b4_transform_verify () = 229 + let test_b4_transform_verify clock = 224 230 let key = Signature.Key.ed25519 ~priv:test_key_ed25519_priv ~pub:test_key_ed25519_pub in 225 231 let uri = Uri.of_string "https://example.org/demo?name1=Value1&Name2=value2" in 226 232 let headers = ··· 231 237 |> Headers.set `Signature "transform=:ZT1kooQsEHpZ0I1IjCqtQppOmIqlJPeo7DHR3SoMn0s5JZ1eRGS0A+vyYP9t/LXlh5QMFFQ6cpLt2m0pmj3NDA==:" 232 238 in 233 239 let context = Signature.Context.request ~method_:`GET ~uri ~headers in 234 - match Signature.verify ~key ~label:"transform" ~context ~headers () with 240 + match Signature.verify ~clock ~key ~label:"transform" ~context ~headers () with 235 241 | Error e -> 236 242 Alcotest.fail ("B.4 Transform verification failed: " ^ Signature.verify_error_to_string e) 237 243 | Ok result -> ··· 242 248 243 249 (** Test that modified messages fail verification - B.4 example where 244 250 method changed from GET to POST should fail *) 245 - let test_b4_transform_modified_fails () = 251 + let test_b4_transform_modified_fails clock = 246 252 let key = Signature.Key.ed25519 ~priv:test_key_ed25519_priv ~pub:test_key_ed25519_pub in 247 253 let uri = Uri.of_string "https://example.org/demo?name1=Value1&Name2=value2" in 248 254 let headers = ··· 254 260 in 255 261 (* Use POST instead of GET - should fail verification *) 256 262 let context = Signature.Context.request ~method_:`POST ~uri ~headers in 257 - match Signature.verify ~key ~label:"transform" ~context ~headers () with 263 + match Signature.verify ~clock ~key ~label:"transform" ~context ~headers () with 258 264 | Error _ -> () (* Expected - signature should not verify *) 259 265 | Ok _ -> Alcotest.fail "B.4 Modified message should have failed verification" 260 266 261 267 (** Test that authority modification fails verification *) 262 - let test_b4_transform_authority_modified_fails () = 268 + let test_b4_transform_authority_modified_fails clock = 263 269 let key = Signature.Key.ed25519 ~priv:test_key_ed25519_priv ~pub:test_key_ed25519_pub in 264 270 (* Changed authority from example.org to example.com *) 265 271 let uri = Uri.of_string "https://example.com/demo?name1=Value1&Name2=value2" in ··· 271 277 |> Headers.set `Signature "transform=:ZT1kooQsEHpZ0I1IjCqtQppOmIqlJPeo7DHR3SoMn0s5JZ1eRGS0A+vyYP9t/LXlh5QMFFQ6cpLt2m0pmj3NDA==:" 272 278 in 273 279 let context = Signature.Context.request ~method_:`GET ~uri ~headers in 274 - match Signature.verify ~key ~label:"transform" ~context ~headers () with 280 + match Signature.verify ~clock ~key ~label:"transform" ~context ~headers () with 275 281 | Error _ -> () (* Expected - signature should not verify *) 276 282 | Ok _ -> Alcotest.fail "B.4 Authority-modified message should have failed verification" 277 283 ··· 333 339 334 340 let hmac_tests = [ 335 341 "B.2.5 HMAC signature base", `Quick, test_b25_hmac_signature_base; 336 - "B.2.5 HMAC verify", `Quick, test_b25_hmac_verify; 342 + "B.2.5 HMAC verify", `Quick, with_clock test_b25_hmac_verify; 337 343 ] 338 344 339 345 let ed25519_tests = [ 340 346 "B.2.6 Ed25519 signature base", `Quick, test_b26_ed25519_signature_base; 341 - "B.2.6 Ed25519 verify", `Quick, test_b26_ed25519_verify; 347 + "B.2.6 Ed25519 verify", `Quick, with_clock test_b26_ed25519_verify; 342 348 ] 343 349 344 350 let transform_tests = [ 345 351 "B.4 Transform signature base", `Quick, test_b4_transform_signature_base; 346 - "B.4 Transform verify", `Quick, test_b4_transform_verify; 347 - "B.4 Modified method fails", `Quick, test_b4_transform_modified_fails; 348 - "B.4 Modified authority fails", `Quick, test_b4_transform_authority_modified_fails; 352 + "B.4 Transform verify", `Quick, with_clock test_b4_transform_verify; 353 + "B.4 Modified method fails", `Quick, with_clock test_b4_transform_modified_fails; 354 + "B.4 Modified authority fails", `Quick, with_clock test_b4_transform_authority_modified_fails; 349 355 ] 350 356 351 357 let component_tests = [
+17 -11
test/test_signature.ml
··· 8 8 module Signature = Requests.Signature 9 9 module Headers = Requests.Headers 10 10 11 + (** Helper to run tests with an Eio clock *) 12 + let with_clock f () = 13 + Eio_main.run @@ fun env -> 14 + let clock = Eio.Stdenv.clock env in 15 + f clock 16 + 11 17 (** {1 Component Tests} *) 12 18 13 19 let test_component_identifiers () = ··· 85 91 86 92 (** {1 HMAC Signing Tests} *) 87 93 88 - let test_hmac_sign_verify () = 94 + let test_hmac_sign_verify clock = 89 95 let secret = "my-secret-key-for-hmac-signing" in 90 96 let key = Signature.Key.symmetric secret in 91 97 let config = Signature.config ~key ~keyid:"test-key-1" ··· 95 101 let uri = Uri.of_string "https://example.com/api/test" in 96 102 let context = Signature.Context.request ~method_:`GET ~uri ~headers in 97 103 98 - match Signature.sign ~config ~context ~headers with 104 + match Signature.sign ~clock ~config ~context ~headers with 99 105 | Error e -> Alcotest.fail ("Signing failed: " ^ Signature.sign_error_to_string e) 100 106 | Ok signed_headers -> 101 107 (* Verify the signature was added *) ··· 105 111 true (Option.is_some (Headers.get `Signature_input signed_headers)); 106 112 107 113 (* Verify the signature *) 108 - match Signature.verify ~key ~context ~headers:signed_headers () with 114 + match Signature.verify ~clock ~key ~context ~headers:signed_headers () with 109 115 | Error e -> Alcotest.fail ("Verification failed: " ^ Signature.verify_error_to_string e) 110 116 | Ok result -> 111 117 Alcotest.(check (option string)) "Keyid matches" ··· 113 119 114 120 (** {1 Ed25519 Signing Tests} *) 115 121 116 - let test_ed25519_sign_verify () = 122 + let test_ed25519_sign_verify clock = 117 123 (* Generate a test Ed25519 key pair *) 118 124 let priv, pub = Mirage_crypto_ec.Ed25519.generate () in 119 125 let priv_bytes = Mirage_crypto_ec.Ed25519.priv_to_octets priv in ··· 132 138 let uri = Uri.of_string "https://api.example.com/v1/resource?id=123" in 133 139 let context = Signature.Context.request ~method_:`POST ~uri ~headers in 134 140 135 - match Signature.sign ~config ~context ~headers with 141 + match Signature.sign ~clock ~config ~context ~headers with 136 142 | Error e -> Alcotest.fail ("Ed25519 signing failed: " ^ Signature.sign_error_to_string e) 137 143 | Ok signed_headers -> 138 144 (* Verify we can verify with the same key *) 139 - match Signature.verify ~key ~context ~headers:signed_headers () with 145 + match Signature.verify ~clock ~key ~context ~headers:signed_headers () with 140 146 | Error e -> Alcotest.fail ("Ed25519 verification failed: " ^ Signature.verify_error_to_string e) 141 147 | Ok result -> 142 148 Alcotest.(check (option string)) "Keyid matches" ··· 146 152 147 153 (** {1 Signature Base Construction Tests} *) 148 154 149 - let test_signature_base_format () = 155 + let test_signature_base_format clock = 150 156 (* Test that the signature base has the correct format *) 151 157 let key = Signature.Key.symmetric "test" in 152 158 let config = Signature.config ~key ~keyid:"test" ··· 158 164 let uri = Uri.of_string "https://example.com/path" in 159 165 let context = Signature.Context.request ~method_:`GET ~uri ~headers in 160 166 161 - match Signature.sign ~config ~context ~headers with 167 + match Signature.sign ~clock ~config ~context ~headers with 162 168 | Error e -> Alcotest.fail ("Signing failed: " ^ Signature.sign_error_to_string e) 163 169 | Ok signed_headers -> 164 170 (* Check Signature-Input format *) ··· 188 194 ] 189 195 190 196 let signing_tests = [ 191 - "HMAC sign and verify", `Quick, test_hmac_sign_verify; 192 - "Ed25519 sign and verify", `Quick, test_ed25519_sign_verify; 193 - "Signature base format", `Quick, test_signature_base_format; 197 + "HMAC sign and verify", `Quick, with_clock test_hmac_sign_verify; 198 + "Ed25519 sign and verify", `Quick, with_clock test_ed25519_sign_verify; 199 + "Signature base format", `Quick, with_clock test_signature_base_format; 194 200 ] 195 201 196 202 let () =