http_test.ml 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. (*
  2. * _ _ ____ _
  3. * _| || |_/ ___| ___ _ __ _ __ ___ | |
  4. * |_ .. _\___ \ / _ \ '_ \| '_ \ / _ \| |
  5. * |_ _|___) | __/ |_) | |_) | (_) |_|
  6. * |_||_| |____/ \___| .__/| .__/ \___/(_)
  7. * |_| |_|
  8. *
  9. * Personal Social Web.
  10. *
  11. * http_test.ml
  12. *
  13. * Copyright (C) The #Seppo contributors. All rights reserved.
  14. *
  15. * This program is free software: you can redistribute it and/or modify
  16. * it under the terms of the GNU General Public License as published by
  17. * the Free Software Foundation, either version 3 of the License, or
  18. * (at your option) any later version.
  19. *
  20. * This program is distributed in the hope that it will be useful,
  21. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  22. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  23. * GNU General Public License for more details.
  24. *
  25. * You should have received a copy of the GNU General Public License
  26. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  27. *)
  28. open Seppo_lib
  29. let test_relpa () =
  30. Http.relpa "a/b/" "a/b/d/e" |> Assrt.equals_string __LOC__ "d/e";
  31. Http.relpa "a/B/" "a/b/d/e" |> Assrt.equals_string __LOC__ ""
  32. let test_uri () =
  33. let base = "https://example.com:443/a/b/c?d=e#f" |> Uri.of_string in
  34. base |> Uri.path
  35. |> Assrt.equals_string __LOC__ "/a/b/c";
  36. "../i.j" |> Uri.of_string |> Http.reso ~base |> Uri.to_string
  37. |> Assrt.equals_string __LOC__ "https://example.com:443/a/i.j";
  38. let re = "https://example.com:443/a/b/C/d.e#ff" |> Uri.of_string |> Http.abs_to_rel ~base in
  39. re |> Uri.to_string |> Assrt.equals_string __LOC__ "C/d.e#ff";
  40. Uri.make ~path:"." () |> Http.reso ~base:Uri.empty |> Uri.to_string |> Assrt.equals_string __LOC__ ""
  41. module Request = struct
  42. let test_uri () =
  43. let abs' (r : Cgi.Request.t) : Uri.t =
  44. Uri.make
  45. ~scheme:r.scheme
  46. ~host:r.host
  47. ~port:(int_of_string r.server_port)
  48. ~path:(r.script_name ^ r.path_info)
  49. ()
  50. and r : Cgi.Request.t = {
  51. content_type = "text/plain";
  52. content_length = None;
  53. host = "example.com";
  54. http_cookie = "";
  55. path_info = "/shaarli";
  56. query_string = "post=uhu";
  57. request_method = "GET";
  58. remote_addr = "127.0.0.1";
  59. scheme = "https";
  60. script_name = "/sub/seppo.cgi";
  61. server_port = "443";
  62. raw_string = Sys.getenv_opt
  63. } in
  64. r |> abs' |> Uri.to_string |> Assrt.equals_string __LOC__ "https://example.com:443/sub/seppo.cgi/shaarli";
  65. r |> Cgi.Request.abs |> Uri.of_string |> Uri.to_string |> Assrt.equals_string __LOC__ "https://example.com/sub/seppo.cgi/shaarli?post=uhu";
  66. r |> Cgi.Request.path_and_query |> Assrt.equals_string __LOC__ "/sub/seppo.cgi/shaarli?post=uhu";
  67. r |> Cgi.Request.path_and_query |> Uri.of_string |> Uri.to_string |> Assrt.equals_string __LOC__ "/sub/seppo.cgi/shaarli?post=uhu";
  68. "a" |> Assrt.equals_string __LOC__ "a";
  69. assert true
  70. let test_query_string () =
  71. match "" |> Uri.query_of_encoded with
  72. | [("",[])] -> ()
  73. | _ -> "no" |> Assrt.equals_string __LOC__ ""
  74. end
  75. module Cookie = struct
  76. let test_rfc1123 () =
  77. let s = "Thu, 01 Jan 1970 00:00:00 GMT" in
  78. Ptime.epoch |> Http.to_rfc1123 |> Assrt.equals_string __LOC__ s;
  79. assert true
  80. let test_to_string () =
  81. let http_only = Some true
  82. and path = Some "seppo.cgi"
  83. and same_site = Some `Strict
  84. and max_age = Some (30. *. 60.)
  85. and secure = Some true in
  86. Cookie.to_string ?path ?secure ?http_only ?same_site ("auth_until", "2022-04-08T22:30:07Z")
  87. |> Assrt.equals_string __LOC__
  88. "auth_until=2022-04-08T22:30:07Z; Path=seppo.cgi; Secure; HttpOnly; \
  89. SameSite=Strict";
  90. Cookie.to_string ?max_age ?path ?secure ?http_only ?same_site ("auth", "yes")
  91. |> Assrt.equals_string __LOC__
  92. "auth=yes; Max-Age=1800; Path=seppo.cgi; Secure; HttpOnly; \
  93. SameSite=Strict";
  94. assert true
  95. let test_of_string () =
  96. let c = Cookie.to_string ("#Seppo!", "foo") in
  97. c |> Assrt.equals_string __LOC__ "#Seppo!=foo";
  98. let v = match c |> Cookie.of_string with
  99. | ("#Seppo!", v) :: [] -> v
  100. | _ -> assert false
  101. in
  102. v |> Assrt.equals_string __LOC__ "foo";
  103. assert true
  104. end
  105. module Form = struct
  106. let test_of_channel () =
  107. let ic = "data/cgi_" ^ "2022-04-05T125146.post" |> open_in in
  108. let fv = ic |> Http.Form.of_channel in
  109. ic |> close_in;
  110. (match fv with
  111. | [ (k0, [ v0 ]); (k1, [ v1 ]); (k2, [ v2 ]); (k3, [ v3 ]) ] ->
  112. k0 |> Assrt.equals_string __LOC__ "login";
  113. v0 |> Assrt.equals_string __LOC__ "demo";
  114. k1 |> Assrt.equals_string __LOC__ "password";
  115. v1 |> Assrt.equals_string __LOC__ "demodemodemo";
  116. k2 |> Assrt.equals_string __LOC__ "token";
  117. v2
  118. |> Assrt.equals_string __LOC__
  119. "ff13e7eaf9541ca2ba30fd44e864c3ff014d2bc9";
  120. k3 |> Assrt.equals_string __LOC__ "returnurl";
  121. v3
  122. |> Assrt.equals_string __LOC__ "https://demo.mro.name/shaarligo/o/p/";
  123. assert true
  124. | _ -> assert false);
  125. (* match
  126. fv
  127. |> Http.Form.filter_sort_keys
  128. [ "login"; "password"; "token"; "returnurl" ]
  129. with
  130. | [ (k0, [ v0 ]); (k1, [ v1 ]); (k2, [ v2 ]); (k3, [ v3 ]) ] ->
  131. k0 |> Assrt.equals_string __LOC__ "login";
  132. v0 |> Assrt.equals_string __LOC__ "demo";
  133. k1 |> Assrt.equals_string __LOC__ "password";
  134. v1 |> Assrt.equals_string __LOC__ "demodemodemo";
  135. k2 |> Assrt.equals_string __LOC__ "returnurl";
  136. v2
  137. |> Assrt.equals_string __LOC__ "https://demo.mro.name/shaarligo/o/p/";
  138. k3 |> Assrt.equals_string __LOC__ "token";
  139. v3
  140. |> Assrt.equals_string __LOC__
  141. "ff13e7eaf9541ca2ba30fd44e864c3ff014d2bc9";
  142. assert true
  143. | _ -> assert false *);
  144. assert true
  145. let test_to_html () =
  146. let defs = [
  147. ("ka", (Ok "va", "text", [("autofocus",""); ("pattern", {|^\S+$|})]));
  148. ] in
  149. (match List.assoc_opt "ka" defs with
  150. | Some (Ok v,_,_) -> v
  151. | _ -> "foo")
  152. |> Assrt.equals_string __LOC__ "va";
  153. assert true
  154. let test_from_html () =
  155. let pred ty valu (na,va) =
  156. Result.bind
  157. valu
  158. (fun v ->
  159. match v with
  160. | None -> Ok None
  161. | Some v as vv ->
  162. match ty,na with
  163. | _,"pattern" ->
  164. Logr.debug (fun m -> m " '%s' ~ /%s/" v va);
  165. Ok vv
  166. | _ ->
  167. Logr.debug (fun m -> m " ignored %s='%s'" na va);
  168. Ok vv)
  169. in
  170. let string (name,(ty,preds)) vals =
  171. let v = Option.bind
  172. (List.assoc_opt name vals)
  173. (fun v -> Some (v |> String.concat "")) in
  174. List.fold_left (pred ty) (Ok v) preds in
  175. let _validate defs vals =
  176. Logr.debug (fun m -> m "Form.validate");
  177. let field init (name,(ty,preds)) =
  178. match string (name,(ty,preds)) vals with
  179. | Error _ as inp ->
  180. (match init with
  181. | Error a -> Error (inp :: a)
  182. | Ok a -> Error (inp :: a)
  183. )
  184. | Ok _ as inp ->
  185. (match init with
  186. | Error a -> Error (inp :: a)
  187. | Ok a -> Ok (inp :: a)
  188. )
  189. in
  190. List.fold_left field (Ok []) defs
  191. in
  192. let def0 = ("ka", ("text", [("autofocus",""); ("pattern", {|^\S+$|})])) in
  193. let _defs = [ def0; ] in
  194. let vals = [
  195. ("ka", ["vb"]);
  196. ] in
  197. (* match _validate defs vals with
  198. | Ok res -> List.assoc_opt "ka" res
  199. |> Option.value ~default:(Ok None)
  200. |> Result.get_ok
  201. |> Option.get
  202. |> Assrt.equals_string __LOC__ "vb"
  203. | _ -> assert false); *)
  204. let ( let* ) = Result.bind in
  205. let run () =
  206. let* k = string def0 vals in
  207. Ok k in
  208. (match run() with
  209. | Ok (Some v) -> v |> Assrt.equals_string __LOC__ "vb"
  210. | _ -> assert true);
  211. assert true
  212. let test_from_html1 () =
  213. let i0 : Http.Form.input = ("k0", "text", [
  214. ("autofocus", "autofocus");
  215. ("required", "required");
  216. ("pattern", {|^[a-z][0-9]+$|});
  217. ]) in
  218. let i1 = ("k1", "text", [
  219. ("required", "required");
  220. ("minlength", "1");
  221. ("maxlength", "50");
  222. ("pattern", {|^v.$|});
  223. ]) in
  224. let vals : Http.Form.t = [
  225. ("k0", ["v0"]);
  226. ("k1", ["v1"]);
  227. ] in
  228. let ( let* ) = Result.bind in
  229. let run () =
  230. let* v0 = vals |> Http.Form.string i0 in
  231. let* v1 = Http.Form.string i1 vals in
  232. v0 |> Assrt.equals_string __LOC__ "v0";
  233. v1 |> Assrt.equals_string __LOC__ "v1";
  234. Ok () in
  235. (match run() with
  236. | Error (_,e) -> e |> Assrt.equals_string __LOC__ ""
  237. | _ -> ())
  238. end
  239. module Header = struct
  240. let test_headers () =
  241. Logr.info (fun m -> m "http_test.test_headers");
  242. let h = [ ("A", "a"); ("B", "b") ] @ [ ("C", "c") ]
  243. |> Cohttp.Header.of_list in
  244. h |> Cohttp.Header.to_string
  245. |> Assrt.equals_string __LOC__ "A: a\r\nB: b\r\nC: c\r\n\r\n";
  246. h |> Cohttp.Header.to_frames
  247. |> String.concat "\n"
  248. |> Assrt.equals_string __LOC__ "A: a\nB: b\nC: c";
  249. Cohttp.Header.get h "a"
  250. |> Option.value ~default:"-"
  251. |> Assrt.equals_string __LOC__ "a";
  252. assert true
  253. let test_signature () =
  254. Logr.info (fun m -> m "http_test.test_signature");
  255. let si = {|keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date",signature="qdx+H7PHHDZgy4y/Ahn9Tny9V3GP6YgBPyUXMmoxWtLbHpUnXS2mg2+SbrQDMCJypxBLSPQR2aAjn7ndmw2iicw3HMbe8VfEdKFYRqzic+efkb3nndiv/x1xSHDJWeSWkx3ButlYSuBskLu6kd9Fswtemr3lgdDEmn04swr2Os0="|} in
  256. let si = Http.Signature.decode si |> Result.get_ok in
  257. si |> List.length |> Assrt.equals_int __LOC__ 4;
  258. assert true
  259. let test_to_sign_string_basic () =
  260. let open Cohttp in
  261. let uri = Uri.of_string "/foo?param=value&pet=dog" in
  262. let request = Some ("post",uri) in
  263. [
  264. ("host", "example.com");
  265. ("date", "Sun, 05 Jan 2014 21:31:40 GMT");
  266. ]
  267. |> Header.of_list
  268. |> Http.Signature.to_sign_string ~request
  269. |> Assrt.equals_string __LOC__
  270. {|(request-target): post /foo?param=value&pet=dog
  271. host: example.com
  272. date: Sun, 05 Jan 2014 21:31:40 GMT|};
  273. assert true
  274. (*
  275. * https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#appendix-C.2
  276. *)
  277. let test_sign_basic () =
  278. Logr.info (fun m -> m "http_test.test_sign_basic");
  279. let pk = match Ap.PubKeyPem.private_of_pem "data/cavage.priv.pem" with
  280. | Ok pk -> pk
  281. | _ -> failwith "ouch" in
  282. let open Cohttp in
  283. let sig_ = "qdx+H7PHHDZgy4y/Ahn9Tny9V3GP6YgBPyUXMmoxWtLbHpUnXS2mg2+SbrQDMCJypxBLSPQR2aAjn7ndmw2iicw3HMbe8VfEdKFYRqzic+efkb3nndiv/x1xSHDJWeSWkx3ButlYSuBskLu6kd9Fswtemr3lgdDEmn04swr2Os0="
  284. and uri = Uri.of_string "/foo?param=value&pet=dog"
  285. and h = [
  286. ("host", "example.com");
  287. ("date", "Sun, 05 Jan 2014 21:31:40 GMT");
  288. ] |> Header.of_list in
  289. let request = Some("post",uri) in
  290. let s = h |> Http.Signature.to_sign_string ~request in
  291. s |> Assrt.equals_string __LOC__
  292. "(request-target): post /foo?param=value&pet=dog\n\
  293. host: example.com\n\
  294. date: Sun, 05 Jan 2014 21:31:40 GMT";
  295. let al,si = s |> Cstruct.of_string |> Ap.PubKeyPem.sign pk in
  296. al |> Assrt.equals_string __LOC__ "rsa-sha256";
  297. si |> Cstruct.to_string |> Base64.encode_exn |> Assrt.equals_string __LOC__ sig_;
  298. Logr.info (fun m -> m "http_test.test_sign_basic II");
  299. let pub = "data/cavage.pub.pem" |> File.to_string |> Ap.PubKeyPem.of_pem |> Result.get_ok in
  300. let uuid = Uuidm.v `V4 in
  301. (match Ap.PubKeyPem.verify ~uuid ~algo:"rsa-sha256" pub si (s |> Cstruct.of_string) with
  302. | Error `Msg e -> e |> Assrt.equals_string __LOC__ ""
  303. | Ok _ -> "ha!" |> Assrt.equals_string __LOC__ "ha!");
  304. assert true
  305. (*
  306. * https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#appendix-C.3
  307. *)
  308. let test_sign_all_headers () =
  309. Logr.info (fun m -> m "http_test.test_sign_all_headers");
  310. let open Cohttp in
  311. let h = [
  312. ("(request-target)", "post /foo?param=value&pet=dog");
  313. ("(created)", "1402170695");
  314. ("(expires)", "1402170699");
  315. ("host", "example.com");
  316. ("date", "Sun, 05 Jan 2014 21:31:40 GMT");
  317. ("content-type", "application/json");
  318. ("digest", "SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=");
  319. ("content-length", "18");
  320. ] |> Header.of_list in
  321. h
  322. |> Header.to_frames
  323. |> String.concat "\n"
  324. |> Assrt.equals_string __LOC__
  325. "(request-target): post /foo?param=value&pet=dog\n\
  326. (created): 1402170695\n\
  327. (expires): 1402170699\n\
  328. host: example.com\n\
  329. date: Sun, 05 Jan 2014 21:31:40 GMT\n\
  330. content-type: application/json\n\
  331. digest: SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=\n\
  332. content-length: 18"
  333. ;
  334. let pk = Ap.PubKeyPem.private_of_pem "data/cavage.priv.pem"
  335. |> Result.get_ok in
  336. let al,si = h
  337. |> Header.to_frames
  338. |> String.concat "\n"
  339. |> Cstruct.of_string
  340. |> Ap.PubKeyPem.sign pk
  341. in
  342. (* |> Assrt.equals_string __LOC__
  343. "vSdrb+dS3EceC9bcwHSo4MlyKS59iFIrhgYkz8+oVLEEzmYZZvRs8rgOp+63LEM3v+MFHB32NfpB2bEKBIvB1q52LaEUHFv120V01IL+TAD48XaERZFukWgHoBTLMhYS2Gb51gWxpeIq8knRmPnYePbF5MOkR0Zkly4zKH7s1dE="
  344. *)
  345. al |> Assrt.equals_string __LOC__ "rsa-sha256";
  346. si |> Cstruct.to_string |> Base64.encode_exn |> Assrt.equals_string __LOC__
  347. "nAkCW0wg9AbbStQRLi8fsS1mPPnA6S5+/0alANcoDFG9hG0bJ8NnMRcB1Sz1eccNMzzLEke7nGXqoiJYZFfT81oaRqh/MNFwQVX4OZvTLZ5xVZQuchRkOSO7b2QX0aFWFOUq6dnwAyliHrp6w3FOxwkGGJPaerw2lOYLdC/Bejk="
  348. let test_signed_headers () =
  349. Logr.info (fun m -> m "http_test.test_signed_headers");
  350. let open Cohttp in
  351. (* values from
  352. https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#appendix-C.3
  353. *)
  354. let id = Uri.of_string "https://example.com/actor/"
  355. and dgst = Some "SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE="
  356. and date,_,_ = Ptime.of_rfc3339 "2014-01-05T22:31:40+01:00" |> Result.get_ok
  357. and uri = Uri.of_string "https://example.com/foo?param=value&pet=dog" in
  358. let key_id = Uri.with_fragment id (Some "main-key")
  359. and pk = match Ap.PubKeyPem.private_of_pem "data/cavage.priv.pem" with
  360. | Ok pk -> pk
  361. | _ -> failwith "ouch" in
  362. Http.signed_headers (key_id,Ap.PubKeyPem.sign pk,date) dgst uri
  363. |> Header.to_frames
  364. |> String.concat "\n"
  365. |> Assrt.equals_string __LOC__
  366. "host: example.com\n\
  367. date: Sun, 05 Jan 2014 21:31:40 GMT\n\
  368. digest: SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=\n\
  369. signature: \
  370. keyId=\"https://example.com/actor/#main-key\",\
  371. algorithm=\"rsa-sha256\",\
  372. headers=\"(request-target) host date digest\",\
  373. signature=\"WC34OEWXgO0viIZAu5qnBcKj5nOMlgjs0ASxgJPYX9x1VtKrYRRhAosH7ixFnkJneSHGn8yY9lowNvbdBg+ZsINx6P0e1WyB0YJbwsREYKYpG1sjwS3R3iCXmXf3m+txiCNhFcbbvb0Grq3wbAWGB0VW7ymI6AHixDXFLD5IYl4=\""
  374. (* https://datatracker.ietf.org/doc/html/rfc7235#appendix-C *)
  375. let test_parse_auth_params () =
  376. Logr.info (fun m -> m "http_test.test_parse_auth_param");
  377. let module P = Http.Signature.P in
  378. (match {|uhu|} |> Tyre.exec (P.token |> Tyre.compile) with
  379. | Ok "uhu" -> "super"
  380. | _ -> "was anderes")
  381. |> Assrt.equals_string __LOC__ "super";
  382. (match {|"uhu"|} |> Tyre.exec (P.quoted_string |> Tyre.compile) with
  383. | Ok "uhu" -> "super"
  384. | _ -> "was anderes")
  385. |> Assrt.equals_string __LOC__ "super";
  386. (match {|uhu="aha"|} |> Tyre.exec (P.auth_param|> Tyre.compile) with
  387. | Ok ("uhu","aha") -> "super"
  388. | _ -> "was anderes")
  389. |> Assrt.equals_string __LOC__ "super";
  390. (match {|uhu="ah\"a"|} |> Tyre.exec (P.auth_param|> Tyre.compile) with
  391. | Ok ("uhu",{|ah"a|}) -> "super"
  392. | _ -> "was anderes")
  393. |> Assrt.equals_string __LOC__ "super";
  394. (match {|a="A", b="B"|} |> Tyre.exec (P.list_auth_param|> Tyre.compile) with
  395. | Ok [("a","A"); ("b","B")] -> "super"
  396. | _ -> "was anderes")
  397. |> Assrt.equals_string __LOC__ "super";
  398. (match {|a="A", nasty="na,s\"ty",b="B"|} |> Tyre.exec (P.list_auth_param|> Tyre.compile) with
  399. | Ok [("a","A");
  400. ("nasty",{|na,s"ty|});
  401. ("b","B")] -> "super"
  402. | _ -> "was anderes")
  403. |> Assrt.equals_string __LOC__ "super";
  404. assert true
  405. let test_parse_signature () =
  406. Logr.info (fun m -> m "http_test.test_parse_signature");
  407. (* https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#section-4.1.1 *)
  408. let _sihe = {|keyId="rsa-key-1",algorithm="hs2019", created=1402170695, expires=1402170995, headers="(request-target) (created) (expires) host date digest content-length", signature="Base64(RSA-SHA256(signing string))"|}
  409. |> Http.Signature.decode in
  410. let _sihe = {|keyId="hmac-key-1",algorithm="hs2019",created=1402170695,headers="(request-target) (created) host digest content-length",signature="Base64(HMAC-SHA512(signing string))"|}
  411. |> Http.Signature.decode in
  412. (*
  413. date='Thu, 29 Jun 2023 09:51:37 GMT' digest='SHA-256=rSBxGz18uv2ZvY9PxjkuKv6ZWR78M/5S2m+yOXrq+ik=' signature='keyId="https://alpaka.social/users/traunstein#main-key",algorithm="rsa-sha256",headers="(request-target) host date digest content-type",signature="JIHBg3VahvgFweniUBfH0QSHOuilcYW313i7H6gptKT/uOSfs5QhADm7LKLZ6q7jZWtQLi4Ge8dhxVeYhGpdU5P3iABn665z3TvuUiwVUO0sGI6yAv+z9wVmFfPLFsTYOB09Fy+yht+E4Z9GOF6C/U79eb/y8QOuj1OJB3L+427IQpnJMuPh5e22LBM1E/eXLbvWyshKqX0n8WZj4qPezzsH21Afn+dUnd2jc2XqUbOpzeFkz45ut0okZAF3686/sQ0sBcloSFfvdB+EuLqZLJSYcnMe3Qe8dUpibgm5+v0XfgLZYPL2P7VpuMXkQB9neRbSCdTWojcABBwUGWV0DA=="'
  414. *)
  415. let h = [
  416. ("date",{|Thu, 29 Jun 2023 09:51:37 GMT|});
  417. ("digest",{|SHA-256=rSBxGz18uv2ZvY9PxjkuKv6ZWR78M/5S2m+yOXrq+ik=|});
  418. ("signature",{|keyId="https://alpaka.social/users/traunstein#main-key",algorithm="rsa-sha256",headers="(request-target) host date digest content-type",signature="JIHBg3VahvgFweniUBfH0QSHOuilcYW313i7H6gptKT/uOSfs5QhADm7LKLZ6q7jZWtQLi4Ge8dhxVeYhGpdU5P3iABn665z3TvuUiwVUO0sGI6yAv+z9wVmFfPLFsTYOB09Fy+yht+E4Z9GOF6C/U79eb/y8QOuj1OJB3L+427IQpnJMuPh5e22LBM1E/eXLbvWyshKqX0n8WZj4qPezzsH21Afn+dUnd2jc2XqUbOpzeFkz45ut0okZAF3686/sQ0sBcloSFfvdB+EuLqZLJSYcnMe3Qe8dUpibgm5+v0XfgLZYPL2P7VpuMXkQB9neRbSCdTWojcABBwUGWV0DA=="|});
  419. ] |> Cohttp.Header.of_list in
  420. let sh = "signature" |> Cohttp.Header.get h |> Option.value ~default:"-" in
  421. sh
  422. |> Assrt.equals_string __LOC__ {|keyId="https://alpaka.social/users/traunstein#main-key",algorithm="rsa-sha256",headers="(request-target) host date digest content-type",signature="JIHBg3VahvgFweniUBfH0QSHOuilcYW313i7H6gptKT/uOSfs5QhADm7LKLZ6q7jZWtQLi4Ge8dhxVeYhGpdU5P3iABn665z3TvuUiwVUO0sGI6yAv+z9wVmFfPLFsTYOB09Fy+yht+E4Z9GOF6C/U79eb/y8QOuj1OJB3L+427IQpnJMuPh5e22LBM1E/eXLbvWyshKqX0n8WZj4qPezzsH21Afn+dUnd2jc2XqUbOpzeFkz45ut0okZAF3686/sQ0sBcloSFfvdB+EuLqZLJSYcnMe3Qe8dUpibgm5+v0XfgLZYPL2P7VpuMXkQB9neRbSCdTWojcABBwUGWV0DA=="|};
  423. (match sh |> Http.Signature.decode
  424. (* Http.Signature.decode *) with
  425. | Ok sh ->
  426. sh |> List.length |> Assrt.equals_int __LOC__ 4;
  427. List.assoc_opt "keyId" sh |> Option.value ~default:"-"
  428. |> Assrt.equals_string __LOC__ "https://alpaka.social/users/traunstein#main-key";
  429. List.assoc_opt "algorithm" sh |> Option.value ~default:"-"
  430. |> Assrt.equals_string __LOC__ "rsa-sha256";
  431. List.assoc_opt "headers" sh |> Option.value ~default:"-"
  432. |> Assrt.equals_string __LOC__ "(request-target) host date digest content-type";
  433. List.assoc_opt "signature" sh |> Option.value ~default:"-"
  434. |> Assrt.equals_string __LOC__ "JIHBg3VahvgFweniUBfH0QSHOuilcYW313i7H6gptKT/uOSfs5QhADm7LKLZ6q7jZWtQLi4Ge8dhxVeYhGpdU5P3iABn665z3TvuUiwVUO0sGI6yAv+z9wVmFfPLFsTYOB09Fy+yht+E4Z9GOF6C/U79eb/y8QOuj1OJB3L+427IQpnJMuPh5e22LBM1E/eXLbvWyshKqX0n8WZj4qPezzsH21Afn+dUnd2jc2XqUbOpzeFkz45ut0okZAF3686/sQ0sBcloSFfvdB+EuLqZLJSYcnMe3Qe8dUpibgm5+v0XfgLZYPL2P7VpuMXkQB9neRbSCdTWojcABBwUGWV0DA=="
  435. | _ -> "fail" |> Assrt.equals_string __LOC__ "");
  436. assert true
  437. let test_verify_basic () =
  438. Logr.info (fun m -> m "http_test.test_verify_basic");
  439. let pub = "data/cavage.pub.pem" |> File.to_string |> Ap.PubKeyPem.of_pem |> Result.get_ok in
  440. let request = Some("post", Uri.of_string "/foo?param=value&pet=dog") in
  441. let h = [
  442. ("some", "bogus");
  443. ("date", {|Sun, 05 Jan 2014 21:31:40 GMT|});
  444. ("signature", {|keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date",signature="qdx+H7PHHDZgy4y/Ahn9Tny9V3GP6YgBPyUXMmoxWtLbHpUnXS2mg2+SbrQDMCJypxBLSPQR2aAjn7ndmw2iicw3HMbe8VfEdKFYRqzic+efkb3nndiv/x1xSHDJWeSWkx3ButlYSuBskLu6kd9Fswtemr3lgdDEmn04swr2Os0="|});
  445. ("more", "bogus");
  446. ("host", {|example.com|});
  447. ] |> Cohttp.Header.of_list in
  448. (* fetch http header values and map from lowercase plus the special name (request-target) *)
  449. let hdr = Cohttp.Header.get h in
  450. (* take a list of header names and fetch them incl. values. *)
  451. let hdrs =
  452. List.fold_left
  453. (fun init k ->
  454. (match hdr k with
  455. | None -> init
  456. | Some v -> Cohttp.Header.add init k v)
  457. )
  458. (Cohttp.Header.init ()) in
  459. let foo () =
  460. Logr.debug (fun m -> m "%s.%s get & parse the signature header" "Ap.Inbox" "post");
  461. let ( let* ) = Result.bind in
  462. let* si_v = "signature" |> hdr |> Option.to_result ~none:Http.s502' in
  463. let* si_v = si_v
  464. |> Http.Signature.decode
  465. |> Result.map_error
  466. (function
  467. | `NoMatch _
  468. | `ConverterFailure _ ->
  469. Logr.debug (fun m -> m "%s.%s Signature parsing failure" "Ap.Inbox" "post");
  470. Http.s502') in
  471. let* algo = si_v |> List.assoc_opt "algorithm" |> Option.to_result ~none:Http.s502' in
  472. let* heads = si_v |> List.assoc_opt "headers" |> Option.to_result ~none:Http.s502' in
  473. let heads = heads |> String.split_on_char ' ' in
  474. let* keyid = si_v |> List.assoc_opt "keyId" |> Option.to_result ~none:Http.s502' in
  475. let _keyid = keyid |> Uri.of_string in
  476. let* sign = si_v |> List.assoc_opt "signature" |> Option.to_result ~none:Http.s502' in
  477. let sign = sign |> Base64.decode_exn |> Cstruct.of_string in
  478. Logr.debug (fun m -> m "%s.%s fetch the remote actor profile & key" "Ap.Inbox" "post");
  479. Logr.debug (fun m -> m "%s.%s get the verified header values, signature algorithm %s" "Ap.Inbox" "post" algo);
  480. let uuid = Uuidm.v `V4 in
  481. let heads = heads |> hdrs in
  482. let* _ = heads
  483. |> Http.Signature.to_sign_string ~request
  484. |> Cstruct.of_string
  485. |> Ap.PubKeyPem.verify ~uuid ~algo pub sign
  486. |> Result.map_error (fun (`Msg e) ->
  487. Logr.warn (fun m -> m "%s.%s %s" "Ap.Inbox" "post" e);
  488. Http.s502') in
  489. Ok heads
  490. in
  491. let v l n = Cohttp.Header.get l n |> Option.value ~default:"?" in
  492. (match foo () with
  493. | Error _ -> "aua" |> Assrt.equals_string __LOC__ "-"
  494. | Ok h->
  495. h |> Cohttp.Header.to_list |> List.length |> Assrt.equals_int __LOC__ 2;
  496. "date" |> v h |> Assrt.equals_string __LOC__ "Sun, 05 Jan 2014 21:31:40 GMT";
  497. "host" |> v h |> Assrt.equals_string __LOC__ "example.com");
  498. assert true
  499. end
  500. let () =
  501. Logr.info (fun m -> m "http_test");
  502. Unix.chdir "../../../test/";
  503. test_relpa ();
  504. test_uri ();
  505. Request.test_uri ();
  506. Request.test_query_string ();
  507. Cookie.test_rfc1123 ();
  508. Cookie.test_to_string ();
  509. Cookie.test_of_string ();
  510. Form.test_of_channel ();
  511. Form.test_to_html ();
  512. Form.test_from_html ();
  513. Form.test_from_html1 ();
  514. Header.test_headers ();
  515. Header.test_signature ();
  516. Header.test_to_sign_string_basic ();
  517. Header.test_sign_basic ();
  518. Header.test_sign_all_headers ();
  519. Header.test_signed_headers ();
  520. Header.test_parse_auth_params ();
  521. Header.test_parse_signature ();
  522. Header.test_verify_basic ();
  523. assert true