2 Commitit 7e676b1e19 ... 6b93c45885

Tekijä SHA1 Viesti Päivämäärä
  Marcus Rohrmoser 6b93c45885 gentle cleanup. 2 kuukautta sitten
  Marcus Rohrmoser 5329c890c4 mini step solidifying http signatures. 2 kuukautta sitten
9 muutettua tiedostoa jossa 155 lisäystä ja 140 poistoa
  1. 2 2
      chkr/cgi.ml
  2. 1 1
      chkr/shell.ml
  3. 3 4
      lib/ap.ml
  4. 107 47
      lib/http.ml
  5. 1 1
      lib/is2s.ml
  6. 3 3
      lib/iweb.ml
  7. 1 1
      lib/main.ml
  8. 1 1
      lib/webfinger.ml
  9. 36 80
      test/t_http.ml

+ 2 - 2
chkr/cgi.ml

@@ -67,7 +67,7 @@ let webfinger _uuid qs =
 let actor _uuid qs (r : Cgi.Request.t)  =
   match qs |> List.assoc_opt "id" with
   | Some [id] ->
-    let key : Http.t_sign_k option =
+    let key =
       (* https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#appendix-C *)
       let pem = {|-----BEGIN RSA PRIVATE KEY-----
 MIICXgIBAAKBgQDCFENGw33yGihy92pDjZQhl0C36rPJj+CvfSC8+q28hxA161QF
@@ -93,7 +93,7 @@ G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI
                |> Cstruct.of_string
                |> Ap.PubKeyPem.private_of_pem_data
                |> Result.get_ok in
-      Some (key_id,Ap.PubKeyPem.sign pk,Ptime_clock.now ())
+      Some (Http.Signature.mkey key_id (Ap.PubKeyPem.sign pk) (Ptime_clock.now ()))
     in
     (match id |> Uri.of_string |> Shell.actor ~key with
      | Error e ->

+ 1 - 1
chkr/shell.ml

@@ -46,7 +46,7 @@ let webfinger acct =
   |> Lwt_main.run
 
 (* TODO add more compliance rules. Check pk. *)
-let actor ?(key : Http.t_sign_k option = None) u =
+let actor ?(key = None) u =
   u
   |> Ap.Actor.http_get ~key
   |> Lwt_main.run

+ 3 - 4
lib/ap.ml

@@ -154,11 +154,10 @@ module PubKeyPem = struct
      * https://discuss.ocaml.org/t/tls-signature-with-opam-tls/9399/9?u=mro
      * https://mirleft.github.io/ocaml-x509/doc/x509/X509/Private_key/#cryptographic-sign-operation
      *)
-    let scheme = `RSA_PKCS1 in
     ("rsa-sha256",
      X509.Private_key.sign
        `SHA256
-       ~scheme
+       ~scheme:`RSA_PKCS1
        pk
        (`Message data)
      |> Result.get_ok)
@@ -218,7 +217,7 @@ module PubKeyPem = struct
 end
 
 module Actor = struct
-  let http_get ?(key : Http.t_sign_k option = None) u =
+  let http_get ?(key = None) u =
     Logr.debug (fun m -> m "%s.%s %a" "Ap.Actor" "http_get" Uri.pp u);
     let%lwt p = u |> Http.get_jsonv ~key Result.ok in
     (match p with
@@ -614,7 +613,7 @@ end
  * https://www.w3.org/TR/activitystreams-core/#media-type
  *)
 
-let send ?(success = `OK) ~(key : Http.t_sign_k) (f_ok : Cohttp.Response.t * string -> unit) to_ msg =
+let send ?(success = `OK) ~key (f_ok : Cohttp.Response.t * string -> unit) to_ msg =
   let body = msg |> Ezjsonm.value_to_string in
   let signed_headers body = PubKeyPem.(Http.signed_headers key (digest_base64' body) to_) in
   let headers = signed_headers body in

+ 107 - 47
lib/http.ml

@@ -276,45 +276,91 @@ module Signature = struct
   end
 
   (** https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#section-4.1 *)
-  let decode s =
-    (* Logr.debug (fun m -> m "%s.%s %s" "Http.Signature" "parse" s); *)
-    Tyre.exec P.list_auth_param' s
+  let decode = Tyre.exec P.list_auth_param'
+
+  (** the header value without escaping e.g. = or "" *)
+  let encode = Tyre.eval P.list_auth_param
 
   let to_sign_string ~request h =
     let h = h |> Cohttp.Header.to_frames in
     (match request with
      | Some (meth,uri) ->
        let s = Printf.sprintf "(request-target): %s %s"
-           (meth |> String.lowercase_ascii)
+           (meth |> Cohttp.Code.string_of_method |> String.lowercase_ascii)
            (uri |> Uri.path_and_query) in
-       h |> List.cons s
+       s :: h
      | _ -> h)
     |> Astring.String.concat ~sep:"\n"
-end
 
-(** 
-   - key_id
-   - signing function
-   - now *)
-type t_sign_k = Uri.t * (Cstruct.t-> string * Cstruct.t) * Ptime.t
+  (** 
+     - key_id
+     - signing function
+     - now *)
+  type t_key = Uri.t * (Cstruct.t-> string * Cstruct.t) * Ptime.t 
+
+  let mkey id fu t : t_key = (id,fu,t)
+
+  (** build the string to sign *)
+  let to_sign_string2
+      (meth : Cohttp.Code.meth)
+      (targ : Uri.t)
+      (hdrs : (string * string) list) =
+    let n,s = [],[] in
+    let n,s = match hdrs |> List.assoc_opt "digest" with
+      | None   -> n,s
+      | Some d -> "digest" :: n,("digest",d) :: s in
+    let n = "(request-target)" :: "host" :: "date" :: n in
+    let s = ("(request-target)",Printf.sprintf "%s %s"
+               (meth |> Cohttp.Code.string_of_method |> Astring.String.map Astring.Char.Ascii.lowercase)
+               (targ |> Uri.path_and_query))
+            :: ("host",targ |> Uri.host |> Option.get)
+            :: ("date",hdrs |> List.assoc "date")
+            :: s in
+    n,s
+
+  let add
+      (priv : X509.Private_key.t)
+      (meth : Cohttp.Code.meth)
+      (targ : Uri.t)
+      (hdrs : (string * string) list) =
+    assert (hdrs |> List.assoc_opt "date" |> Option.is_some);
+    assert (targ |> Uri.host |> Option.is_some);
+    assert (hdrs |> List.assoc_opt "host" |> Option.is_some);
+    assert (hdrs |> List.assoc "host" |> Astring.String.equal (targ |> Uri.host_with_default ~default:""));
+    let n,s = to_sign_string2 meth targ hdrs in
+    let s = s |> Cohttp.Header.of_list |> Cohttp.Header.to_frames |> Astring.String.concat ~sep:"\n" in
+    let n = n |> Astring.String.concat ~sep:" " in
+    (* build the signature header value *)
+    match `Message (s |> Cstruct.of_string) |> X509.Private_key.sign `SHA256 ~scheme:`RSA_PKCS1 priv with
+    | Error _ as e -> e
+    | Ok s ->
+      let v = [
+        "signature", s |> Cstruct.to_string |> Base64.encode_string;
+        "headers"  ,n;
+        "algorithm","rsa-sha256";
+      ]
+        |> List.fold_left (fun init (k,v) -> Printf.sprintf {|%s="%s"|} k v :: init) []
+        |> Astring.String.concat ~sep:"," in
+      Ok ( hdrs @ ["signature",v] )
+end
 
 (** Create headers including a signature for a POST request.
- *
- * https://blog.joinmastodon.org/2018/06/how-to-implement-a-basic-activitypub-server/#http-signatures
- * https://socialhub.activitypub.rocks/t/help-needed-http-signatures/2458
- * https://tools.ietf.org/id/draft-cavage-http-signatures-12.html
- *
- * HTTP signature according https://tools.ietf.org/id/draft-cavage-http-signatures-12.html#rfc.appendix.C
- * https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-10.html#name-creating-a-signature
- * Digest http://tools.ietf.org/html/rfc3230#section-4.3.2
- *
- * https://docs.joinmastodon.org/spec/security/#http
- * https://w3id.org/security#publicKey
- * https://w3id.org/security/v1
- *
- * NOT: https://datatracker.ietf.org/doc/draft-ietf-httpbis-message-signatures/
+
+    https://blog.joinmastodon.org/2018/06/how-to-implement-a-basic-activitypub-server/#http-signatures
+    https://socialhub.activitypub.rocks/t/help-needed-http-signatures/2458
+    https://tools.ietf.org/id/draft-cavage-http-signatures-12.html
+
+    HTTP signature according https://tools.ietf.org/id/draft-cavage-http-signatures-12.html#rfc.appendix.C
+    https://www.ietf.org/archive/id/draft-ietf-httpbis-message-signatures-10.html#name-creating-a-signature
+    Digest http://tools.ietf.org/html/rfc3230#section-4.3.2
+
+    https://docs.joinmastodon.org/spec/security/#http
+    https://w3id.org/security#publicKey
+    https://w3id.org/security/v1
+
+    NOT: https://datatracker.ietf.org/doc/draft-ietf-httpbis-message-signatures/
 *)
-let signed_headers (key_id,(fkt_sign : Cstruct.t -> string * Cstruct.t),date : t_sign_k) dige uri =
+let signed_headers (key_id,fkt_sign,date : Signature.t_key) dige uri =
   let open Cohttp in
   let hdr = (
     ("host", uri |> Uri.host |> Option.value ~default:"-") ::
@@ -322,27 +368,41 @@ let signed_headers (key_id,(fkt_sign : Cstruct.t -> string * Cstruct.t),date : t
     match dige with
     | None      -> []
     | Some dige -> ("digest", dige) :: []
-  ) |> Header.of_list in
-  let meth,dige = match dige with
-    | None   -> "get", ""
-    | Some _ -> "post"," digest"
+  ) in
+  let meth,lst = match dige with
+    | None   -> `GET, ""
+    | Some _ -> `POST," digest"
   in
+  (*
+  let _n,tx_ = Signature.to_sign_string2 meth uri hdr in
+  let tx_ = tx_ |> Cohttp.Header.of_list |> Cohttp.Header.to_frames |> Astring.String.concat ~sep:"\n" in
+  assert (tx_ |> String.equal tx');
+  *)
   let request = Some (meth,uri) in
-  let algo,(sgna : Cstruct.t) = hdr
-                                |> Signature.to_sign_string ~request
-                                |> Cstruct.of_string
-                                |> fkt_sign in
-  Printf.sprintf (* must be symmetric to Signature.decode *)
-    "keyId=\"%s\",\
-     algorithm=\"%s\",\
-     headers=\"(request-target) host date%s\",\
-     signature=\"%s\""
-    (key_id |> Uri.to_string)
-    algo
-    dige
-    (sgna |> Cstruct.to_string |> Base64.encode_exn)
-  |> Header.add hdr "signature"
-(* Logr.debug (fun m -> m "%s.%s\n%s" "Http" "signed_headers" (r |> Header.to_string)); *)
+  let tx' = hdr
+            |> Header.of_list
+            |> Signature.to_sign_string ~request in
+  let algo,sgna = tx'
+                  |> Cstruct.of_string
+                  |> fkt_sign in
+  ["keyId",     key_id |> Uri.to_string ;
+   "algorithm", algo ;
+   "headers",   "(request-target) host date" ^ lst ;
+   "signature",  sgna |> Cstruct.to_string |> Base64.encode_exn ;
+  ]
+  |> Signature.encode
+     (*
+     Printf.sprintf (* must be symmetric to Signature.decode *)
+       "keyId=\"%s\",\
+        algorithm=\"%s\",\
+        headers=\"(request-target) host date%s\",\
+        signature=\"%s\""
+       (key_id |> Uri.to_string)
+       algo
+       lst
+       (sgna |> Cstruct.to_string |> Base64.encode_exn)
+       *)
+  |> Header.add (hdr |> Header.of_list) "signature"
 
 (* https://github.com/mirage/ocaml-cohttp#dealing-with-timeouts *)
 let timeout ~seconds ~f =
@@ -357,7 +417,7 @@ let timeout ~seconds ~f =
 
 (* don't care about maximum redirects but rather enforce a timeout *)
 let get
-    ?(key : t_sign_k option = None)
+    ?(key = None)
     ?(seconds = 5.0)
     ?(headers = Cohttp.Header.init())
     uri =
@@ -422,7 +482,7 @@ let post
   r
 
 let get_jsonv
-    ?(key : t_sign_k option = None)
+    ?(key = None)
     ?(seconds = 5.0)
     ?(headers = [ H.acc_app_jlda ] |> Cohttp.Header.of_list)
     fkt

+ 1 - 1
lib/is2s.ml

@@ -86,7 +86,7 @@ module Inbox = struct
         Http.s500') in
     let   me      = Uri.make ~path:Ap.proj () |> Http.reso ~base in
     let   mekeyid = me |> Ap.Person.my_key_id in
-    let   mekey : Http.t_sign_k = mekeyid,Ap.PubKeyPem.sign pk,now in
+    let   mekey = Http.Signature.mkey mekeyid (Ap.PubKeyPem.sign pk) now in
     (* don't queue it but re-try in case *)
     (* dereferencing okeyid must yield an actor profile document. *)
     let%lwt siac = Ap.Actor.http_get ~key:(Some mekey) okeyid in

+ 3 - 3
lib/iweb.ml

@@ -531,7 +531,7 @@ module Actor = struct
           let date = Ptime_clock.now () in
           let base = base () in
           let key_id = Uri.make ~path:Ap.proj () |> Http.reso ~base |> Ap.Person.my_key_id in
-          let key : Http.t_sign_k option = Some (key_id,Ap.PubKeyPem.sign pk,date) in
+          let key = Some (Http.Signature.mkey key_id (Ap.PubKeyPem.sign pk) date) in
           let%lwt act = u
                         |> Uri.of_string
                         |> Ap.Actor.http_get ~key in
@@ -683,7 +683,7 @@ module Actor = struct
            let date = Ptime_clock.now () in
            let base = base () in
            let key_id = Uri.make ~path:Ap.proj () |> Http.reso ~base |> Ap.Person.my_key_id in
-           let key : Http.t_sign_k option = Some (key_id,Ap.PubKeyPem.sign pk,date) in
+           let key = Some (Http.Signature.mkey key_id (Ap.PubKeyPem.sign pk) date) in
            let%lwt act =
              u
              |> Uri.of_string
@@ -799,7 +799,7 @@ module Http_ = struct
           Logr.err (fun m -> m "%s %s.%s %s" E.e1009 "Iweb.Http_" "get" s);
           Http.s500') in
       Logr.debug (fun m -> m "%s.%s my keyid %a" "Iweb.Http_" "get" Uri.pp_hum keyid);
-      let key : Http.t_sign_k option = Some (keyid,Ap.PubKeyPem.sign pk,now) in
+      let key = Some (Http.Signature.mkey keyid (Ap.PubKeyPem.sign pk) now) in
 
       let headers = [ Http.H.acc_app_jlda ] |> Cohttp.Header.of_list in
       let%lwt p = u

+ 1 - 1
lib/main.ml

@@ -46,7 +46,7 @@ let post_signed
     uri =
   Logr.debug (fun m -> m "%s.%s %a key_id: %a" "Main" "post_signed" Uuidm.pp uuid Uri.pp key_id);
   assert (key_id |> Uri.to_string |> St.is_suffix ~affix:"/actor.jsa#main-key");
-  let key : Http.t_sign_k = (key_id,Ap.PubKeyPem.sign pk,date) in
+  let key = Http.Signature.mkey key_id (Ap.PubKeyPem.sign pk) date in
   let he_sig = (Http.signed_headers key (Ap.PubKeyPem.digest_base64' body) uri) in
   let headers = Cohttp.Header.add_list he_sig headers in
   Http.post ~headers body uri

+ 1 - 1
lib/webfinger.ml

@@ -54,7 +54,7 @@ let apa = "activitypub/actor.jsa" (* redeclare Ap.proj to avoid dependency cycle
 *)
 module Client = struct
   let http_get
-      ?(key : Http.t_sign_k option = None)
+      ?(key = None)
       (w : Uri.t) =
     let mape (_ : Ezjsonm.value Decoders__Error.t) =
       Logr.err (fun m -> m "%s: webfinger decode failed %a" E.e1027

+ 36 - 80
test/t_http.ml

@@ -199,12 +199,25 @@ module Header = struct
     |> Assrt.equals_string __LOC__ "a";
     assert true
 
+  let tc_sig_encode () =
+    [ "k1","v1";
+      "k2","v2"; ] |> Http.Signature.encode
+    |> check string __LOC__ {|k1="v1",k2="v2"|};
+    `GET
+    |> Cohttp.Code.string_of_method
+    |> Astring.String.map Astring.Char.Ascii.lowercase
+    |> check string __LOC__ "get"
+
   let tc_signature () =
     Logr.info (fun m -> m "http_test.test_signature");
     let si = {|keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date",signature="qdx+H7PHHDZgy4y/Ahn9Tny9V3GP6YgBPyUXMmoxWtLbHpUnXS2mg2+SbrQDMCJypxBLSPQR2aAjn7ndmw2iicw3HMbe8VfEdKFYRqzic+efkb3nndiv/x1xSHDJWeSWkx3ButlYSuBskLu6kd9Fswtemr3lgdDEmn04swr2Os0="|} in
-    let si = Http.Signature.decode si |> Result.get_ok in
-    si |> List.length |> Assrt.equals_int __LOC__ 4;
-    assert true
+    let si' = si 
+              |> Http.Signature.decode
+              |> Result.get_ok in
+    si' |> List.length |> Assrt.equals_int __LOC__ 4;
+    si'
+    |> Tyre.eval Http.Signature.P.list_auth_param
+    |> check string __LOC__ si
 
   let priv_key_cavage =
     {|-----BEGIN RSA PRIVATE KEY-----
@@ -238,95 +251,38 @@ oYi+1hqp1fIekaxsyQIDAQAB
     |> X509.Public_key.decode_pem |> Result.get_ok
 
   let tc_sign2 () =
-    (*
-    which interface?
-
-    A: - (request-target) method + uri
-       - signing function: string -> string
-       - list of headers' names to sign: string list
-       - all headers list (or cohttp)
-       return: header list incl. signature header
-
-    B: - (request-target) method + uri
-       - signing function: string -> string
-       - to sign headers list (or cohttp)
-       return: header list incl. signature header
-
-    *)
-    (** build the string to sign *)
-    let to_sign_string
-        (meth : Cohttp.Code.meth)
-        (targ : Uri.t)
-        (hdrs : (string * string) list) =
-      let n,s = [],[] in
-      let n,s = match hdrs |> List.assoc_opt "Digest" with
-        | None   -> n,s
-        | Some d -> "digest" :: n,("digest",d) :: s in
-      let n = "(request-target)" :: "host" :: "date" :: n in
-      let s = ("(request-target)",Printf.sprintf "%s %s"
-                 (meth |> Cohttp.Code.string_of_method |> Astring.String.map Astring.Char.Ascii.lowercase)
-                 (targ |> Uri.path_and_query))
-              :: ("host",targ |> Uri.host |> Option.get)
-              :: ("date",hdrs |> List.assoc "Date")
-              :: s in
-      n,s
-    in
-    let add_signature
-        (priv : X509.Private_key.t)
-        (meth : Cohttp.Code.meth)
-        (targ : Uri.t)
-        (hdrs : (string * string) list) =
-      assert (hdrs |> List.assoc_opt "Date" |> Option.is_some);
-      assert (hdrs |> List.assoc "Host" |> Astring.String.equal (targ |> Uri.host_with_default ~default:""));
-      let n,s = to_sign_string meth targ hdrs in
-      let s = s |> Cohttp.Header.of_list |> Cohttp.Header.to_frames |> Astring.String.concat ~sep:"\n" in
-      let n = n |> Astring.String.concat ~sep:" " in
-      (* build the signature header value *)
-      match `Message (s |> Cstruct.of_string) |> X509.Private_key.sign `SHA256 ~scheme:`RSA_PKCS1 priv with
-      | Error _ as e -> e
-      | Ok s ->
-        let v = [
-          "signature", s |> Cstruct.to_string |> Base64.encode_string;
-          "headers"  ,n;
-          "algorithm","rsa-sha256";
-        ]
-          |> List.fold_left (fun init (k,v) -> Printf.sprintf {|%s="%s"|} k v :: init) []
-          |> Astring.String.concat ~sep:"," in
-        (* add it to the header list *)
-        Ok ( ("Signature",v) :: hdrs ) in
     `GET |> Cohttp.Code.string_of_method |> check string __LOC__ "GET";
-    let n,s = ["Date","today";
-               "Digest","SHA256=0815"]
-              |> to_sign_string
+    let n,s = ["date","today";
+               "digest","SHA256=0815"]
+              |> Http.Signature.to_sign_string2
                 `GET
-                ("https://example.com/uhu?foo=bar#baz" |> Uri.of_string)
-    in
+                ("https://example.com/uhu?foo=bar#baz" |> Uri.of_string) in
     let s = s |> Cohttp.Header.of_list |> Cohttp.Header.to_frames |> Astring.String.concat ~sep:"\n" in
     let n = n |> Astring.String.concat ~sep:" " in
     n |> check string __LOC__ "(request-target) host date digest";
     s |> check string __LOC__ "(request-target): get /uhu?foo=bar\nhost: example.com\ndate: today\ndigest: SHA256=0815";
-    ["Host","example.com";
-     "Date","today";
-     "Digest","SHA256=0815"]
-    |> add_signature
+    ["host","example.com";
+     "date","today";
+     "digest","SHA256=0815"]
+    |> Http.Signature.add
       priv_key_cavage
       `GET
       ("https://example.com/uhu?foo=bar#baz" |> Uri.of_string)
     |> Result.get_ok
     |> Cohttp.Header.of_list
     |> Cohttp.Header.to_frames |> Astring.String.concat ~sep:"\n"
-    |> check string __LOC__ {|Signature: algorithm="rsa-sha256",headers="(request-target) host date digest",signature="AFq6XChsi63zuCVVzeVigx7BV/HzHnsg304i9uqJ44t2QufQ4WvYS1jDh2B539B3VyBQiuXoiNrSssMoShVORmZzA1y4dnnFlYncFdQRsRDRA//E2YB39ECSby0Fl6pBK+Ws/090RWcxFxTBFsD0H9JQuVASbBCDxy2lhHTFugg="
-Host: example.com
-Date: today
-Digest: SHA256=0815|}
+    |> check string __LOC__ {|host: example.com
+date: today
+digest: SHA256=0815
+signature: algorithm="rsa-sha256",headers="(request-target) host date digest",signature="AFq6XChsi63zuCVVzeVigx7BV/HzHnsg304i9uqJ44t2QufQ4WvYS1jDh2B539B3VyBQiuXoiNrSssMoShVORmZzA1y4dnnFlYncFdQRsRDRA//E2YB39ECSby0Fl6pBK+Ws/090RWcxFxTBFsD0H9JQuVASbBCDxy2lhHTFugg="|}
 
   let tc_to_sign_string_basic () =
     let open Cohttp in
     let uri = Uri.of_string "/foo?param=value&pet=dog" in
-    let request = Some ("post",uri) in
+    let request = Some (`POST,uri) in
     [
-      ("host", "example.com");
-      ("date", "Sun, 05 Jan 2014 21:31:40 GMT");
+      "host", "example.com" ;
+      "date", "Sun, 05 Jan 2014 21:31:40 GMT" ;
     ]
     |> Header.of_list
     |> Http.Signature.to_sign_string ~request
@@ -339,7 +295,6 @@ date: Sun, 05 Jan 2014 21:31:40 GMT|};
 (*
  * https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#appendix-C.2
  *)
-
   let tc_sign_basic () =
     Logr.info (fun m -> m "http_test.test_sign_basic");
     let pk = priv_key_cavage in
@@ -350,7 +305,7 @@ date: Sun, 05 Jan 2014 21:31:40 GMT|};
       ("host", "example.com");
       ("date", "Sun, 05 Jan 2014 21:31:40 GMT");
     ] |> Header.of_list in
-    let request = Some("post",uri) in
+    let request = Some(`POST,uri) in
     let s = h |> Http.Signature.to_sign_string ~request in
     s |> Assrt.equals_string __LOC__
       "(request-target): post /foo?param=value&pet=dog\n\
@@ -505,7 +460,7 @@ date: Sun, 05 Jan 2014 21:31:40 GMT|};
   let tc_verify_basic () =
     Logr.info (fun m -> m "http_test.test_verify_basic");
     let pub = pub_key_cavage in
-    let request = Some("post", Uri.of_string "/foo?param=value&pet=dog") in
+    let request = Some(`POST, Uri.of_string "/foo?param=value&pet=dog") in
     let h = [
       ("some", "bogus");
       ("date", {|Sun, 05 Jan 2014 21:31:40 GMT|});
@@ -574,7 +529,7 @@ host: dev.seppo.social
 date: Mon, 16 Sep 2024 12:26:45 GMT
 digest: SHA-256=bcLAcfJg/048pSOMeA29j/PqW2iQJw96mT+egmjF+Zk='
       *)
-    let request = Some ("post","https://dev.seppo.social/2024-03-19/seppo.cgi/activitypub/inbox.jsa" |> Uri.of_string) in
+    let request = Some (`POST,"https://dev.seppo.social/2024-03-19/seppo.cgi/activitypub/inbox.jsa" |> Uri.of_string) in
     let h = [
       ("host", {|dev.seppo.social|});
       ("date", {|Mon, 16 Sep 2024 12:26:45 GMT|});
@@ -628,7 +583,7 @@ digest: SHA-256=bcLAcfJg/048pSOMeA29j/PqW2iQJw96mT+egmjF+Zk='
       ("signature", {|keyId="https://gotosocial.dev.seppo.social/users/demo/main-key",algorithm="hs2019",headers="(request-target) host date digest",signature="MG9tIV9rWJHDFKEFGsjakYoBtPjZbyk/ddTn6Xr2xHkmTVZDmkJmGcD4yDfWfQ4m8BYS+jd4lnb8O5fdm/pFwpFDGU70IDLsg6INGxZJQKuWbQB7dFEBJt22h8GcjOIlXvw4cKsgc3KvplIjTrFlnYiQQVvcSy+uQRXJTJTm2Y6vxOQzFvSJa0S8lXz5+x/CqpqXJtj1cSztEHZEFdBla2M30smV1uJvQcfa+lIRPwXdwtL0COsg8J00hAYBoFXPo+4N/jytArkYOFz6MasUrRODURuAE2fR6JI2aAerBy0WFE17TyWuXjWlnYt6t9aO5Wzo/qc/3DgMWHQr8NZGVg=="|});
       ("digest", {|SHA-256=bcLAcfJg/048pSOMeA29j/PqW2iQJw96mT+egmjF+Zk=|});
     ] |> Cohttp.Header.of_list in
-    let request = Some ("post","" |> Uri.of_string) in
+    let request = Some (`POST,"" |> Uri.of_string) in
     h |> Http.Signature.to_sign_string ~request
     |> Assrt.equals_string __LOC__ {|(request-target): post /
 host: dev.seppo.social
@@ -787,6 +742,7 @@ let () =
       "tc_to_string",                      `Quick, Cookie.tc_to_string;
       "tc_of_string",                      `Quick, Cookie.tc_of_string;
       "tc_headers",                        `Quick, Header.tc_headers;
+      "tc_sig_encode",                     `Quick, Header.tc_sig_encode;
       "tc_signature",                      `Quick, Header.tc_signature;
       "tc_sign2",                          `Quick, Header.tc_sign2;
       "tc_to_sign_string_basic",           `Quick, Header.tc_to_sign_string_basic;