t_http.ml 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830
  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 Alcotest
  29. open Seppo_lib
  30. let set_up () =
  31. Mirage_crypto_rng_unix.use_default ();
  32. Unix.chdir "../../../test/"
  33. let tc_relpa () =
  34. Http.relpa "a/b/" "a/b/d/e" |> Assrt.equals_string __LOC__ "d/e";
  35. Http.relpa "a/B/" "a/b/d/e" |> Assrt.equals_string __LOC__ "";
  36. let base = Uri.of_string "a/b/" in
  37. Uri.resolve "" base (Uri.of_string "c/d/") |> Uri.to_string |> check string __LOC__ "a/b/c/d/";
  38. Uri.resolve "" base ((Http.relpa (base |> Uri.to_string) ("a/b/c/d/")) |> Uri.of_string) |> Uri.to_string |> check string __LOC__ "a/b/c/d/";
  39. ()
  40. let tc_uri () =
  41. let base = "https://example.com:443/a/b/c?d=e#f" |> Uri.of_string in
  42. base |> Uri.path
  43. |> Assrt.equals_string __LOC__ "/a/b/c";
  44. "../i.j" |> Uri.of_string |> Http.reso ~base |> Uri.to_string
  45. |> Assrt.equals_string __LOC__ "https://example.com:443/a/i.j";
  46. let re = "https://example.com:443/a/b/C/d.e#ff" |> Uri.of_string |> Http.abs_to_rel ~base in
  47. re |> Uri.to_string |> Assrt.equals_string __LOC__ "C/d.e#ff";
  48. Uri.make ~path:"." () |> Http.reso ~base:Uri.empty |> Uri.to_string |> Assrt.equals_string __LOC__ "";
  49. let a = Uri.of_string "https://example.com/path" in
  50. assert ("https://example.com/path" |> Uri.of_string |> Uri.equal a);
  51. assert ("https://EXAMPLE.com/path" |> Uri.of_string |> Uri.equal a);
  52. ()
  53. let tc_rel_cd () =
  54. "/seppo.cgi" |> Cgi.cd_cgi_bin_twin_path |> Assrt.equals_string __LOC__ ".";
  55. "/sub/dir/seppo.cgi" |> Cgi.cd_cgi_bin_twin_path |> Assrt.equals_string __LOC__ ".";
  56. "/cgi-bin/seppo.cgi" |> Cgi.cd_cgi_bin_twin_path |> Assrt.equals_string __LOC__ "..";
  57. "/cgi-bin/sub/dir/seppo.cgi" |> Cgi. cd_cgi_bin_twin_path |> Assrt.equals_string __LOC__ "../../../sub/dir"
  58. module Request = struct
  59. let tc_rx_script_name () =
  60. let chk n fn =
  61. if Str.string_match Cgi.Request.rx_script_name fn 0
  62. then (
  63. fn |> Str.matched_group 5 |> Assrt.equals_string __LOC__ n;
  64. fn |> Str.matched_group 3 )
  65. else "no match"
  66. in
  67. "/seppo.cgi" |> chk "seppo.cgi" |> Assrt.equals_string __LOC__ "/";
  68. "/cgi-bin/seppo.cgi" |> chk "seppo.cgi" |> Assrt.equals_string __LOC__ "/";
  69. "/cgi-bin/uhu/seppo.cgi" |> chk "seppo.cgi" |> Assrt.equals_string __LOC__ "/uhu/";
  70. "/apchk.cgi" |> chk "apchk.cgi" |> Assrt.equals_string __LOC__ "/";
  71. "/cgi-bin/uhu/apchk.cgi" |> chk "apchk.cgi" |> Assrt.equals_string __LOC__ "/uhu/";
  72. ()
  73. let tc_uri () =
  74. let r : Cgi.Request.t = {
  75. content_type = "text/plain";
  76. content_length = None;
  77. host = "example.com";
  78. http_cookie = "";
  79. path_info = "/shaarli";
  80. query_string = "post=uhu";
  81. request_method = "GET";
  82. remote_addr = "127.0.0.1";
  83. scheme = "https";
  84. script_name = "/sub/seppo.cgi";
  85. server_port = "443";
  86. raw_string = Sys.getenv_opt
  87. } in
  88. r |> Cgi.Request.abs |> Uri.to_string |> Assrt.equals_string __LOC__ "https://example.com/sub/seppo.cgi/shaarli?post=uhu";
  89. r |> Cgi.Request.path_and_query |> Uri.to_string |> Assrt.equals_string __LOC__ "/sub/seppo.cgi/shaarli?post=uhu";
  90. "a" |> Assrt.equals_string __LOC__ "a";
  91. assert true
  92. let tc_base () =
  93. Uri.make ~scheme:"https" ~host:"example.com" ()
  94. |> Cgi.Request.base' "/seppo.cgi"
  95. |> Uri.to_string
  96. |> Assrt.equals_string __LOC__ "https://example.com/";
  97. Uri.make ~scheme:"https" ~host:"example.com" ()
  98. |> Cgi.Request.base' "/a/b/seppo.cgi"
  99. |> Uri.to_string
  100. |> Assrt.equals_string __LOC__ "https://example.com/a/b/";
  101. let r : Cgi.Request.t = {
  102. content_type = "text/plain";
  103. content_length = None;
  104. host = "example.com";
  105. http_cookie = "";
  106. path_info = "/shaarli";
  107. query_string = "post=uhu";
  108. request_method = "GET";
  109. remote_addr = "127.0.0.1";
  110. scheme = "https";
  111. script_name = "/cgi-bin/sub/seppo.cgi";
  112. server_port = "443";
  113. raw_string = Sys.getenv_opt
  114. } in
  115. r |> Cgi.Request.base
  116. |> Uri.to_string
  117. |> Assrt.equals_string __LOC__ "https://example.com/sub/";
  118. {r with script_name = "/sib/seppo.cgi"}
  119. |> Cgi.Request.base
  120. |> Uri.to_string
  121. |> Assrt.equals_string __LOC__ "https://example.com/sib/";
  122. {r with script_name = "/seppo.cgi"}
  123. |> Cgi.Request.base
  124. |> Uri.to_string
  125. |> Assrt.equals_string __LOC__ "https://example.com/";
  126. ()
  127. let tc_query_string () =
  128. match "" |> Uri.query_of_encoded with
  129. | [("",[])] -> ()
  130. | _ -> "no" |> Assrt.equals_string __LOC__ ""
  131. end
  132. module Cookie = struct
  133. let tc_rfc1123 () =
  134. let s = "Thu, 01 Jan 1970 00:00:00 GMT" in
  135. Ptime.epoch |> Http.to_rfc1123 |> Assrt.equals_string __LOC__ s;
  136. assert true
  137. let tc_to_string () =
  138. let http_only = Some true
  139. and path = Some "seppo.cgi"
  140. and same_site = Some `Strict
  141. and max_age = Some (30. *. 60.)
  142. and secure = Some true in
  143. Cookie.to_string ?path ?secure ?http_only ?same_site ("auth_until", "2022-04-08T22:30:07Z")
  144. |> Assrt.equals_string __LOC__
  145. "auth_until=2022-04-08T22:30:07Z; Path=seppo.cgi; Secure; HttpOnly; \
  146. SameSite=Strict";
  147. Cookie.to_string ?max_age ?path ?secure ?http_only ?same_site ("auth", "yes")
  148. |> Assrt.equals_string __LOC__
  149. "auth=yes; Max-Age=1800; Path=seppo.cgi; Secure; HttpOnly; \
  150. SameSite=Strict";
  151. assert true
  152. let tc_of_string () =
  153. let c = Cookie.to_string ("#Seppo!", "foo") in
  154. c |> Assrt.equals_string __LOC__ "#Seppo!=foo";
  155. let v = match c |> Cookie.of_string with
  156. | ("#Seppo!", v) :: [] -> v
  157. | _ -> assert false
  158. in
  159. v |> Assrt.equals_string __LOC__ "foo";
  160. assert true
  161. end
  162. module Header = struct
  163. let tc_headers () =
  164. Logr.info (fun m -> m "http_test.test_headers");
  165. let h = [ ("A", "a"); ("B", "b") ] @ [ ("C", "c") ]
  166. |> Cohttp.Header.of_list in
  167. h |> Cohttp.Header.to_string
  168. |> Assrt.equals_string __LOC__ "A: a\r\nB: b\r\nC: c\r\n\r\n";
  169. h |> Cohttp.Header.to_frames
  170. |> String.concat "\n"
  171. |> Assrt.equals_string __LOC__ "A: a\nB: b\nC: c";
  172. Cohttp.Header.get h "a"
  173. |> Option.value ~default:"-"
  174. |> Assrt.equals_string __LOC__ "a";
  175. assert true
  176. let tc_sig_encode () =
  177. [ "k1","v1";
  178. "k2","v2"; ] |> Http.Signature.encode
  179. |> check string __LOC__ {|k1="v1",k2="v2"|};
  180. `GET
  181. |> Cohttp.Code.string_of_method
  182. |> Astring.String.map Astring.Char.Ascii.lowercase
  183. |> check string __LOC__ "get"
  184. let tc_signature () =
  185. Logr.info (fun m -> m "http_test.test_signature");
  186. let si = {|keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date",signature="qdx+H7PHHDZgy4y/Ahn9Tny9V3GP6YgBPyUXMmoxWtLbHpUnXS2mg2+SbrQDMCJypxBLSPQR2aAjn7ndmw2iicw3HMbe8VfEdKFYRqzic+efkb3nndiv/x1xSHDJWeSWkx3ButlYSuBskLu6kd9Fswtemr3lgdDEmn04swr2Os0="|} in
  187. let si' = si
  188. |> Http.Signature.decode
  189. |> Result.get_ok in
  190. si' |> List.length |> Assrt.equals_int __LOC__ 4;
  191. si'
  192. |> Tyre.eval Http.Signature.P.list_auth_param
  193. |> check string __LOC__ si
  194. let priv_key_cavage =
  195. {|-----BEGIN RSA PRIVATE KEY-----
  196. MIICXgIBAAKBgQDCFENGw33yGihy92pDjZQhl0C36rPJj+CvfSC8+q28hxA161QF
  197. NUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6Z4UMR7EOcpfdUE9Hf3m/hs+F
  198. UR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJwoYi+1hqp1fIekaxsyQIDAQAB
  199. AoGBAJR8ZkCUvx5kzv+utdl7T5MnordT1TvoXXJGXK7ZZ+UuvMNUCdN2QPc4sBiA
  200. QWvLw1cSKt5DsKZ8UETpYPy8pPYnnDEz2dDYiaew9+xEpubyeW2oH4Zx71wqBtOK
  201. kqwrXa/pzdpiucRRjk6vE6YY7EBBs/g7uanVpGibOVAEsqH1AkEA7DkjVH28WDUg
  202. f1nqvfn2Kj6CT7nIcE3jGJsZZ7zlZmBmHFDONMLUrXR/Zm3pR5m0tCmBqa5RK95u
  203. 412jt1dPIwJBANJT3v8pnkth48bQo/fKel6uEYyboRtA5/uHuHkZ6FQF7OUkGogc
  204. mSJluOdc5t6hI1VsLn0QZEjQZMEOWr+wKSMCQQCC4kXJEsHAve77oP6HtG/IiEn7
  205. kpyUXRNvFsDE0czpJJBvL/aRFUJxuRK91jhjC68sA7NsKMGg5OXb5I5Jj36xAkEA
  206. gIT7aFOYBFwGgQAQkWNKLvySgKbAZRTeLBacpHMuQdl1DfdntvAyqpAZ0lY0RKmW
  207. G6aFKaqQfOXKCyWoUiVknQJAXrlgySFci/2ueKlIE1QqIiLSZ8V8OlpFLRnb1pzI
  208. 7U1yQXnTAEFYM560yJlzUpOb1V4cScGd365tiSMvxLOvTA==
  209. -----END RSA PRIVATE KEY-----
  210. |}
  211. |> X509.Private_key.decode_pem |> Result.get_ok
  212. let pub_key_cavage =
  213. {|-----BEGIN PUBLIC KEY-----
  214. MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCFENGw33yGihy92pDjZQhl0C3
  215. 6rPJj+CvfSC8+q28hxA161QFNUd13wuCTUcq0Qd2qsBe/2hFyc2DCJJg0h1L78+6
  216. Z4UMR7EOcpfdUE9Hf3m/hs+FUR45uBJeDK1HSFHD8bHKD6kv8FPGfJTotc+2xjJw
  217. oYi+1hqp1fIekaxsyQIDAQAB
  218. -----END PUBLIC KEY-----
  219. |}
  220. |> X509.Public_key.decode_pem |> Result.get_ok
  221. let tc_sign2 () =
  222. `GET |> Cohttp.Code.string_of_method |> check string __LOC__ "GET";
  223. let n,s = ["date","today";
  224. "digest","SHA256=0815"]
  225. |> Http.Signature.to_sign_string
  226. `GET
  227. ("https://example.com/uhu?foo=bar#baz" |> Uri.of_string) in
  228. n |> check string __LOC__ "(request-target) host date digest";
  229. s |> check string __LOC__ "(request-target): get /uhu?foo=bar\nhost: example.com\ndate: today\ndigest: SHA256=0815";
  230. ["host","example.com";
  231. "date","today";
  232. "digest","SHA256=0815"]
  233. |> Http.Signature.add
  234. priv_key_cavage
  235. `GET
  236. ("https://example.com/uhu?foo=bar#baz" |> Uri.of_string)
  237. |> Result.get_ok
  238. |> Cohttp.Header.of_list
  239. |> Cohttp.Header.to_frames |> Astring.String.concat ~sep:"\n"
  240. |> check string __LOC__ {|host: example.com
  241. date: today
  242. digest: SHA256=0815
  243. signature: algorithm="rsa-sha256",headers="(request-target) host date digest",signature="AFq6XChsi63zuCVVzeVigx7BV/HzHnsg304i9uqJ44t2QufQ4WvYS1jDh2B539B3VyBQiuXoiNrSssMoShVORmZzA1y4dnnFlYncFdQRsRDRA//E2YB39ECSby0Fl6pBK+Ws/090RWcxFxTBFsD0H9JQuVASbBCDxy2lhHTFugg="|}
  244. let tc_to_sign_string_basic () =
  245. let open Cohttp in
  246. let uri = Uri.of_string "/foo?param=value&pet=dog" in
  247. [
  248. "host", "example.com" ;
  249. "date", "Sun, 05 Jan 2014 21:31:40 GMT" ;
  250. ]
  251. |> Header.of_list
  252. |> Http.Signature.to_sign_string0 ~request:(Some (`POST,uri))
  253. |> Assrt.equals_string __LOC__
  254. {|(request-target): post /foo?param=value&pet=dog
  255. host: example.com
  256. date: Sun, 05 Jan 2014 21:31:40 GMT|};
  257. assert true
  258. (*
  259. * https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#appendix-C.2
  260. *)
  261. let tc_sign_basic () =
  262. Logr.info (fun m -> m "http_test.test_sign_basic");
  263. let pk = priv_key_cavage in
  264. let open Cohttp in
  265. let sig_ = "qdx+H7PHHDZgy4y/Ahn9Tny9V3GP6YgBPyUXMmoxWtLbHpUnXS2mg2+SbrQDMCJypxBLSPQR2aAjn7ndmw2iicw3HMbe8VfEdKFYRqzic+efkb3nndiv/x1xSHDJWeSWkx3ButlYSuBskLu6kd9Fswtemr3lgdDEmn04swr2Os0="
  266. and uri = Uri.of_string "/foo?param=value&pet=dog"
  267. and h = [
  268. "host", "example.com" ;
  269. "date", "Sun, 05 Jan 2014 21:31:40 GMT" ;
  270. ] |> Header.of_list in
  271. let s = h |> Http.Signature.to_sign_string0 ~request:(Some (`POST,uri)) in
  272. s |> Assrt.equals_string __LOC__
  273. "(request-target): post /foo?param=value&pet=dog\n\
  274. host: example.com\n\
  275. date: Sun, 05 Jan 2014 21:31:40 GMT";
  276. let al,si = s |> Ap.PubKeyPem.sign pk in
  277. al |> Assrt.equals_string __LOC__ "rsa-sha256";
  278. si |> Base64.encode_exn |> Assrt.equals_string __LOC__ sig_;
  279. Logr.info (fun m -> m "http_test.test_sign_basic II");
  280. let pub = pub_key_cavage in
  281. (match Ap.PubKeyPem.verify ~algo:"rsa-sha256" ~inbox:Uri.empty ~key:pub ~signature:si s with
  282. | Error `Msg e -> e |> Assrt.equals_string __LOC__ ""
  283. | Ok _ -> "ha!" |> Assrt.equals_string __LOC__ "ha!");
  284. assert true
  285. (*
  286. * https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#appendix-C.3
  287. *)
  288. let tc_sign_all_headers () =
  289. Logr.info (fun m -> m "http_test.test_sign_all_headers");
  290. let open Cohttp in
  291. let h = [
  292. ("(request-target)", "post /foo?param=value&pet=dog");
  293. ("(created)", "1402170695");
  294. ("(expires)", "1402170699");
  295. ("host", "example.com");
  296. ("date", "Sun, 05 Jan 2014 21:31:40 GMT");
  297. ("content-type", "application/json");
  298. ("digest", "SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=");
  299. ("content-length", "18");
  300. ] |> Header.of_list in
  301. h
  302. |> Header.to_frames
  303. |> String.concat "\n"
  304. |> Assrt.equals_string __LOC__
  305. "(request-target): post /foo?param=value&pet=dog\n\
  306. (created): 1402170695\n\
  307. (expires): 1402170699\n\
  308. host: example.com\n\
  309. date: Sun, 05 Jan 2014 21:31:40 GMT\n\
  310. content-type: application/json\n\
  311. digest: SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=\n\
  312. content-length: 18"
  313. ;
  314. let pk = priv_key_cavage in
  315. let al,si = h
  316. |> Header.to_frames
  317. |> String.concat "\n"
  318. |> Ap.PubKeyPem.sign pk
  319. in
  320. (* |> Assrt.equals_string __LOC__
  321. "vSdrb+dS3EceC9bcwHSo4MlyKS59iFIrhgYkz8+oVLEEzmYZZvRs8rgOp+63LEM3v+MFHB32NfpB2bEKBIvB1q52LaEUHFv120V01IL+TAD48XaERZFukWgHoBTLMhYS2Gb51gWxpeIq8knRmPnYePbF5MOkR0Zkly4zKH7s1dE="
  322. *)
  323. al |> Assrt.equals_string __LOC__ "rsa-sha256";
  324. si |> Base64.encode_exn |> Assrt.equals_string __LOC__
  325. "nAkCW0wg9AbbStQRLi8fsS1mPPnA6S5+/0alANcoDFG9hG0bJ8NnMRcB1Sz1eccNMzzLEke7nGXqoiJYZFfT81oaRqh/MNFwQVX4OZvTLZ5xVZQuchRkOSO7b2QX0aFWFOUq6dnwAyliHrp6w3FOxwkGGJPaerw2lOYLdC/Bejk="
  326. let tc_signed_headers () =
  327. Logr.info (fun m -> m "http_test.test_signed_headers");
  328. let open Cohttp in
  329. (* values from
  330. https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#appendix-C.3
  331. *)
  332. let id = Uri.of_string "https://example.com/actor/"
  333. and dgst = Some "SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE="
  334. and date,_,_ = Ptime.of_rfc3339 "2014-01-05T22:31:40+01:00" |> Result.get_ok
  335. and uri = Uri.of_string "https://example.com/foo?param=value&pet=dog" in
  336. let key_id = Uri.with_fragment id (Some "main-key")
  337. and pk = priv_key_cavage in
  338. Http.signed_headers (key_id,pk,date) dgst uri
  339. |> Header.to_frames
  340. |> String.concat "\n"
  341. |> Assrt.equals_string __LOC__
  342. "host: example.com\n\
  343. date: Sun, 05 Jan 2014 21:31:40 GMT\n\
  344. digest: SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=\n\
  345. signature: \
  346. keyId=\"https://example.com/actor/#main-key\",\
  347. algorithm=\"rsa-sha256\",\
  348. headers=\"(request-target) host date digest\",\
  349. signature=\"WC34OEWXgO0viIZAu5qnBcKj5nOMlgjs0ASxgJPYX9x1VtKrYRRhAosH7ixFnkJneSHGn8yY9lowNvbdBg+ZsINx6P0e1WyB0YJbwsREYKYpG1sjwS3R3iCXmXf3m+txiCNhFcbbvb0Grq3wbAWGB0VW7ymI6AHixDXFLD5IYl4=\""
  350. (* https://datatracker.ietf.org/doc/html/rfc7235#appendix-C *)
  351. let tc_parse_auth_params () =
  352. Logr.info (fun m -> m "http_test.test_parse_auth_param");
  353. let module P = Http.Signature.P in
  354. (match {|uhu|} |> Tyre.exec (P.token |> Tyre.compile) with
  355. | Ok "uhu" -> "super"
  356. | _ -> "was anderes")
  357. |> Assrt.equals_string __LOC__ "super";
  358. (match {|"uhu"|} |> Tyre.exec (P.quoted_string |> Tyre.compile) with
  359. | Ok "uhu" -> "super"
  360. | _ -> "was anderes")
  361. |> Assrt.equals_string __LOC__ "super";
  362. (match {|uhu="aha"|} |> Tyre.exec (P.auth_param|> Tyre.compile) with
  363. | Ok ("uhu","aha") -> "super"
  364. | _ -> "was anderes")
  365. |> Assrt.equals_string __LOC__ "super";
  366. (match {|uhu="ah\"a"|} |> Tyre.exec (P.auth_param|> Tyre.compile) with
  367. | Ok ("uhu",{|ah"a|}) -> "super"
  368. | _ -> "was anderes")
  369. |> Assrt.equals_string __LOC__ "super";
  370. (match {|a="A", b="B"|} |> Tyre.exec (P.list_auth_param|> Tyre.compile) with
  371. | Ok [("a","A"); ("b","B")] -> "super"
  372. | _ -> "was anderes")
  373. |> Assrt.equals_string __LOC__ "super";
  374. (match {|a="A", nasty="na,s\"ty",b="B"|} |> Tyre.exec (P.list_auth_param|> Tyre.compile) with
  375. | Ok [("a","A");
  376. ("nasty",{|na,s"ty|});
  377. ("b","B")] -> "super"
  378. | _ -> "was anderes")
  379. |> Assrt.equals_string __LOC__ "super";
  380. assert true
  381. let tc_parse_signature () =
  382. Logr.info (fun m -> m "http_test.test_parse_signature");
  383. (* https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#section-4.1.1 *)
  384. 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))"|}
  385. |> Http.Signature.decode in
  386. let _sihe = {|keyId="hmac-key-1",algorithm="hs2019",created=1402170695,headers="(request-target) (created) host digest content-length",signature="Base64(HMAC-SHA512(signing string))"|}
  387. |> Http.Signature.decode in
  388. (*
  389. 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=="'
  390. *)
  391. let h = [
  392. ("date",{|Thu, 29 Jun 2023 09:51:37 GMT|});
  393. ("digest",{|SHA-256=rSBxGz18uv2ZvY9PxjkuKv6ZWR78M/5S2m+yOXrq+ik=|});
  394. ("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=="|});
  395. ] |> Cohttp.Header.of_list in
  396. let sh = "signature" |> Cohttp.Header.get h |> Option.value ~default:"-" in
  397. sh
  398. |> 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=="|};
  399. (match sh |> Http.Signature.decode
  400. (* Http.Signature.decode *) with
  401. | Ok sh ->
  402. sh |> List.length |> Assrt.equals_int __LOC__ 4;
  403. List.assoc_opt "keyId" sh |> Option.value ~default:"-"
  404. |> Assrt.equals_string __LOC__ "https://alpaka.social/users/traunstein#main-key";
  405. List.assoc_opt "algorithm" sh |> Option.value ~default:"-"
  406. |> Assrt.equals_string __LOC__ "rsa-sha256";
  407. List.assoc_opt "headers" sh |> Option.value ~default:"-"
  408. |> Assrt.equals_string __LOC__ "(request-target) host date digest content-type";
  409. List.assoc_opt "signature" sh |> Option.value ~default:"-"
  410. |> Assrt.equals_string __LOC__ "JIHBg3VahvgFweniUBfH0QSHOuilcYW313i7H6gptKT/uOSfs5QhADm7LKLZ6q7jZWtQLi4Ge8dhxVeYhGpdU5P3iABn665z3TvuUiwVUO0sGI6yAv+z9wVmFfPLFsTYOB09Fy+yht+E4Z9GOF6C/U79eb/y8QOuj1OJB3L+427IQpnJMuPh5e22LBM1E/eXLbvWyshKqX0n8WZj4qPezzsH21Afn+dUnd2jc2XqUbOpzeFkz45ut0okZAF3686/sQ0sBcloSFfvdB+EuLqZLJSYcnMe3Qe8dUpibgm5+v0XfgLZYPL2P7VpuMXkQB9neRbSCdTWojcABBwUGWV0DA=="
  411. | _ -> "fail" |> Assrt.equals_string __LOC__ "");
  412. assert true
  413. let tc_verify_basic () =
  414. Logr.info (fun m -> m "http_test.test_verify_basic");
  415. let pub = pub_key_cavage in
  416. let h = [
  417. ("some", "bogus");
  418. ("date", {|Sun, 05 Jan 2014 21:31:40 GMT|});
  419. ("signature", {|keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date",signature="qdx+H7PHHDZgy4y/Ahn9Tny9V3GP6YgBPyUXMmoxWtLbHpUnXS2mg2+SbrQDMCJypxBLSPQR2aAjn7ndmw2iicw3HMbe8VfEdKFYRqzic+efkb3nndiv/x1xSHDJWeSWkx3ButlYSuBskLu6kd9Fswtemr3lgdDEmn04swr2Os0="|});
  420. ("more", "bogus");
  421. ("host", {|example.com|});
  422. ] |> Cohttp.Header.of_list in
  423. (* fetch http header values and map from lowercase plus the special name (request-target) *)
  424. let hdr = Cohttp.Header.get h in
  425. (* take a list of header names and fetch them incl. values. *)
  426. let hdrs =
  427. List.fold_left
  428. (fun init k ->
  429. (match hdr k with
  430. | None -> init
  431. | Some v -> Cohttp.Header.add init k v)
  432. )
  433. (Cohttp.Header.init ()) in
  434. let foo () =
  435. Logr.debug (fun m -> m "%s.%s get & parse the signature header" "Ap.Inbox" "post");
  436. let ( let* ) = Result.bind in
  437. let* si_v = "signature" |> hdr |> Option.to_result ~none:Http.s502' in
  438. let* si_v = si_v
  439. |> Http.Signature.decode
  440. |> Result.map_error
  441. (function
  442. | `NoMatch _
  443. | `ConverterFailure _ ->
  444. Logr.debug (fun m -> m "%s.%s Signature parsing failure" "Ap.Inbox" "post");
  445. Http.s502') in
  446. let* algo = si_v |> List.assoc_opt "algorithm" |> Option.to_result ~none:Http.s502' in
  447. let* heads = si_v |> List.assoc_opt "headers" |> Option.to_result ~none:Http.s502' in
  448. let heads = heads |> String.split_on_char ' ' in
  449. let* keyid = si_v |> List.assoc_opt "keyId" |> Option.to_result ~none:Http.s502' in
  450. let _keyid = keyid |> Uri.of_string in
  451. let* sign = si_v |> List.assoc_opt "signature" |> Option.to_result ~none:Http.s502' in
  452. let sign = sign |> Base64.decode_exn in
  453. Logr.debug (fun m -> m "%s.%s fetch the remote actor profile & key" "Ap.Inbox" "post");
  454. Logr.debug (fun m -> m "%s.%s get the verified header values, signature algorithm %s" "Ap.Inbox" "post" algo);
  455. let heads = heads |> hdrs in
  456. let* _ = heads
  457. |> Http.Signature.to_sign_string0 ~request:(Some (`POST,Uri.of_string "/foo?param=value&pet=dog"))
  458. |> Ap.PubKeyPem.verify ~algo ~inbox:Uri.empty ~key:pub ~signature:sign
  459. |> Result.map_error (fun (`Msg e) ->
  460. Logr.warn (fun m -> m "%s.%s %s" "Ap.Inbox" "post" e);
  461. Http.s502') in
  462. Ok heads
  463. in
  464. let v l n = Cohttp.Header.get l n |> Option.value ~default:"?" in
  465. (match foo () with
  466. | Error _ -> "aua" |> Assrt.equals_string __LOC__ "-"
  467. | Ok h->
  468. h |> Cohttp.Header.to_list |> List.length |> Assrt.equals_int __LOC__ 2;
  469. "date" |> v h |> Assrt.equals_string __LOC__ "Sun, 05 Jan 2014 21:31:40 GMT";
  470. "host" |> v h |> Assrt.equals_string __LOC__ "example.com");
  471. assert true
  472. let tc_verify_hs2019_raw () =
  473. (*
  474. 2024-09-16T14:26:45.455+02:00 DEBUG Is2s.Inbox.post ba111224-c9b7-4c56-ae48-82f04795f23e 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=="
  475. 2024-09-16T14:26:45.608+02:00 DEBUG Is2s.Inbox.post signature check '(request-target): post /2024-03-19/seppo.cgi/activitypub/inbox.jsa
  476. host: dev.seppo.social
  477. date: Mon, 16 Sep 2024 12:26:45 GMT
  478. digest: SHA-256=bcLAcfJg/048pSOMeA29j/PqW2iQJw96mT+egmjF+Zk='
  479. *)
  480. let h = [
  481. ("host", {|dev.seppo.social|});
  482. ("date", {|Mon, 16 Sep 2024 12:26:45 GMT|});
  483. ("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=="|});
  484. ("digest", {|SHA-256=bcLAcfJg/048pSOMeA29j/PqW2iQJw96mT+egmjF+Zk=|});
  485. ] |> Cohttp.Header.of_list in
  486. let pay = h |> Http.Signature.to_sign_string0
  487. ~request:(Some (`POST,
  488. "https://dev.seppo.social/2024-03-19/seppo.cgi/activitypub/inbox.jsa" |> Uri.of_string)) in
  489. pay |> Assrt.equals_string __LOC__ {|(request-target): post /2024-03-19/seppo.cgi/activitypub/inbox.jsa
  490. host: dev.seppo.social
  491. date: Mon, 16 Sep 2024 12:26:45 GMT
  492. 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=="
  493. digest: SHA-256=bcLAcfJg/048pSOMeA29j/PqW2iQJw96mT+egmjF+Zk=|};
  494. let signature = "signature"
  495. |> Cohttp.Header.get h
  496. |> Option.value ~default:"ouch"
  497. |> Http.Signature.decode
  498. |> Result.get_ok
  499. |> List.assoc "signature" in
  500. signature |> check string __LOC__ {|MG9tIV9rWJHDFKEFGsjakYoBtPjZbyk/ddTn6Xr2xHkmTVZDmkJmGcD4yDfWfQ4m8BYS+jd4lnb8O5fdm/pFwpFDGU70IDLsg6INGxZJQKuWbQB7dFEBJt22h8GcjOIlXvw4cKsgc3KvplIjTrFlnYiQQVvcSy+uQRXJTJTm2Y6vxOQzFvSJa0S8lXz5+x/CqpqXJtj1cSztEHZEFdBla2M30smV1uJvQcfa+lIRPwXdwtL0COsg8J00hAYBoFXPo+4N/jytArkYOFz6MasUrRODURuAE2fR6JI2aAerBy0WFE17TyWuXjWlnYt6t9aO5Wzo/qc/3DgMWHQr8NZGVg==|};
  501. let signature = signature
  502. |> Base64.decode_exn in
  503. let pe = {|{"@context":["https://www.w3.org/ns/activitystreams","http://schema.org","http://joinmastodon.org/ns","https://w3id.org/security/v1"],"attachment":[{"name":"reason","type":"PropertyValue","value":"\u003ca href=\"https://seppo.social/issues/13\" rel=\"nofollow noreferrer noopener\" target=\"_blank\"\u003ehttps://seppo.social/issues/13\u003c/a\u003e"},{"name":"2","type":"PropertyValue","value":"b"},{"name":"3","type":"PropertyValue","value":"c"},{"name":"4","type":"PropertyValue","value":"d"}],"discoverable":false,"featured":"https://gotosocial.dev.seppo.social/users/demo/collections/featured","followers":"https://gotosocial.dev.seppo.social/users/demo/followers","following":"https://gotosocial.dev.seppo.social/users/demo/following","id":"https://gotosocial.dev.seppo.social/users/demo","inbox":"https://gotosocial.dev.seppo.social/users/demo/inbox","manuallyApprovesFollowers":true,"name":"slöth","outbox":"https://gotosocial.dev.seppo.social/users/demo/outbox","preferredUsername":"demo","publicKey":{"id":"https://gotosocial.dev.seppo.social/users/demo/main-key","owner":"https://gotosocial.dev.seppo.social/users/demo","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxaVct4ctlG5ssph8+Qiu\ncV3/mtoTGE3nyl+9eTUaIvqdgcedIvDrAqJ4Koj6JEFMTjHpJYBvWXWhR7QEuYZJ\n3HUjcZozxh40zCOosu841uvoIGEDq75xHRKhFRC38RBo7PRhAjqqCYgfkt7AERmw\nJBA1BF2EAPI5i5mjo2IPZzAJdByweVZoo5Wo3Ki3utF3oPXZRnuHmtLbphdJxZTD\n9MXfaZLG6sfuzv+Og7O6AninawBmYFz60d8GwfaBOKi8ftbnheh04BGir+jgE02m\nWNgdMWOBM9D0D3+kmZ2+m+rACQCT9D500EtMWZSn4ZBDQxUkLLbXrZJt5/fDRur7\nQQIDAQAB\n-----END PUBLIC KEY-----\n"},"summary":"\u003cp\u003e\u003ca href=\"https://seppo.social/issues/13\" rel=\"nofollow noreferrer noopener\" target=\"_blank\"\u003ehttps://seppo.social/issues/13\u003c/a\u003e\u003c/p\u003e","tag":[],"type":"Person","url":"https://gotosocial.dev.seppo.social/@demo"}|}
  504. |> Ezjsonm.from_string
  505. |> As2_vocab.Decode.person
  506. |> Result.get_ok in
  507. let pub = pe.public_key.pem |> Ap.PubKeyPem.of_pem |> Result.get_ok in
  508. let `Msg e = X509.Public_key.verify
  509. `SHA256
  510. ~scheme:`RSA_PKCS1
  511. ~signature
  512. pub
  513. (`Message pay)
  514. |> Result.get_error in
  515. e |> check string __LOC__ "bad signature"
  516. let tc_verify_hs2019 () =
  517. (*
  518. 2024-09-16T14:26:45.455+02:00 DEBUG Is2s.Inbox.post ba111224-c9b7-4c56-ae48-82f04795f23e 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=="
  519. 2024-09-16T14:26:45.608+02:00 DEBUG Is2s.Inbox.post signature check '(request-target): post /2024-03-19/seppo.cgi/activitypub/inbox.jsa
  520. host: dev.seppo.social
  521. date: Mon, 16 Sep 2024 12:26:45 GMT
  522. digest: SHA-256=bcLAcfJg/048pSOMeA29j/PqW2iQJw96mT+egmjF+Zk='
  523. *)
  524. let act = {|{"@context":["https://www.w3.org/ns/activitystreams","http://schema.org","http://joinmastodon.org/ns","https://w3id.org/security/v1"],"attachment":[{"name":"reason","type":"PropertyValue","value":"\u003ca href=\"https://seppo.social/issues/13\" rel=\"nofollow noreferrer noopener\" target=\"_blank\"\u003ehttps://seppo.social/issues/13\u003c/a\u003e"},{"name":"2","type":"PropertyValue","value":"b"},{"name":"3","type":"PropertyValue","value":"c"},{"name":"4","type":"PropertyValue","value":"d"}],"discoverable":false,"featured":"https://gotosocial.dev.seppo.social/users/demo/collections/featured","followers":"https://gotosocial.dev.seppo.social/users/demo/followers","following":"https://gotosocial.dev.seppo.social/users/demo/following","id":"https://gotosocial.dev.seppo.social/users/demo","inbox":"https://gotosocial.dev.seppo.social/users/demo/inbox","manuallyApprovesFollowers":true,"name":"slöth","outbox":"https://gotosocial.dev.seppo.social/users/demo/outbox","preferredUsername":"demo","publicKey":{"id":"https://gotosocial.dev.seppo.social/users/demo/main-key","owner":"https://gotosocial.dev.seppo.social/users/demo","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxaVct4ctlG5ssph8+Qiu\ncV3/mtoTGE3nyl+9eTUaIvqdgcedIvDrAqJ4Koj6JEFMTjHpJYBvWXWhR7QEuYZJ\n3HUjcZozxh40zCOosu841uvoIGEDq75xHRKhFRC38RBo7PRhAjqqCYgfkt7AERmw\nJBA1BF2EAPI5i5mjo2IPZzAJdByweVZoo5Wo3Ki3utF3oPXZRnuHmtLbphdJxZTD\n9MXfaZLG6sfuzv+Og7O6AninawBmYFz60d8GwfaBOKi8ftbnheh04BGir+jgE02m\nWNgdMWOBM9D0D3+kmZ2+m+rACQCT9D500EtMWZSn4ZBDQxUkLLbXrZJt5/fDRur7\nQQIDAQAB\n-----END PUBLIC KEY-----\n"},"summary":"\u003cp\u003e\u003ca href=\"https://seppo.social/issues/13\" rel=\"nofollow noreferrer noopener\" target=\"_blank\"\u003ehttps://seppo.social/issues/13\u003c/a\u003e\u003c/p\u003e","tag":[],"type":"Person","url":"https://gotosocial.dev.seppo.social/@demo"}|} in
  525. let pe = act |> Ezjsonm.from_string |> As2_vocab.Decode.person |> Result.get_ok in
  526. let pub = pe.public_key.pem |> Ap.PubKeyPem.of_pem |> Result.get_ok in
  527. let h = [
  528. ("host", {|dev.seppo.social|});
  529. ("date", {|Mon, 16 Sep 2024 12:26:45 GMT|});
  530. ("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=="|});
  531. ("digest", {|SHA-256=bcLAcfJg/048pSOMeA29j/PqW2iQJw96mT+egmjF+Zk=|});
  532. ] |> Cohttp.Header.of_list in
  533. h |> Http.Signature.to_sign_string0 ~request:(Some (`POST,"/" |> Uri.of_string))
  534. |> Assrt.equals_string __LOC__ {|(request-target): post /
  535. host: dev.seppo.social
  536. date: Mon, 16 Sep 2024 12:26:45 GMT
  537. 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=="
  538. digest: SHA-256=bcLAcfJg/048pSOMeA29j/PqW2iQJw96mT+egmjF+Zk=|};
  539. let si = "signature"
  540. |> Cohttp.Header.get h |> Option.value ~default:"ouch"
  541. |> Http.Signature.decode
  542. |> Result.get_ok in
  543. si |> List.length |> check int __LOC__ 4;
  544. si |> List.assoc "keyId" |> check string __LOC__ "https://gotosocial.dev.seppo.social/users/demo/main-key";
  545. si |> List.assoc "algorithm" |> check string __LOC__ "hs2019";
  546. let sign = si |> List.assoc "signature" |> Base64.decode_exn in
  547. let inbox = "/" |> Uri.of_string in
  548. let `Msg m = h
  549. |> Http.Signature.to_sign_string0 ~request:(Some (`GET,Uri.empty))
  550. |> Ap.PubKeyPem.verify ~algo:"hs2019" ~inbox ~key:pub ~signature:sign
  551. |> Result.get_error in
  552. m |> check string __LOC__ "bad signature"
  553. (* also https://datatracker.ietf.org/doc/id/draft-richanna-http-message-signatures-00.html#section-a.3
  554. 2024-10-03T00:14:44.022+02:00 DEBUG Is2s.Inbox.post 837a994a-754b-49bd-9154-982603e3dcc7 Signature: keyId="https://gotosocial.dev.seppo.social/users/demo/main-key",algorithm="hs2019",headers="(request-target) host date digest",signature="HCnGYTDX+xfmuUysOGBN//mqInBv//55S/1NRZ+vEqMZxIgu7QsmUB/I7MfeM3PyF1oZDZ5cngsLPmuSjBVnQkAOJIebybNsh9HyLT5Ln4UDyiY30AZVuX+tNz0K5eGmnxS9LFPyfihvrnYZN+2Irny5mCPkB61u8TTmjYKG/WTLKrVhf49fwNt6U11zq7xkVkB8NT6VllH4tftx/GpfqvdCl+FA+UrtKu6GyHBQMmsEz7ybcVXhF04K8z95X2nM/I/pfmQ/b2ySpzX3YwL0UlVrFI44fq7zXIvpkKT3ntg66z3xluuhBL3y2amzty6Ciz/evcJcq6JpaVJ2jTNO5Q=="
  555. 2024-10-03T00:14:44.205+02:00 DEBUG Is2s.Inbox.post signature check '(request-target): post /2024-03-19/seppo.cgi/activitypub/inbox.jsa
  556. host: mx250.darknet.mro.name
  557. date: Wed, 02 Oct 2024 22:14:43 GMT
  558. digest: SHA-256=DpWuW0JAXqss/tShNxYG7NmD+o18Ok7lNDvHRD0vbcU='
  559. *)
  560. let tc_verify_hs2019_b () =
  561. let signature = {|HCnGYTDX+xfmuUysOGBN//mqInBv//55S/1NRZ+vEqMZxIgu7QsmUB/I7MfeM3PyF1oZDZ5cngsLPmuSjBVnQkAOJIebybNsh9HyLT5Ln4UDyiY30AZVuX+tNz0K5eGmnxS9LFPyfihvrnYZN+2Irny5mCPkB61u8TTmjYKG/WTLKrVhf49fwNt6U11zq7xkVkB8NT6VllH4tftx/GpfqvdCl+FA+UrtKu6GyHBQMmsEz7ybcVXhF04K8z95X2nM/I/pfmQ/b2ySpzX3YwL0UlVrFI44fq7zXIvpkKT3ntg66z3xluuhBL3y2amzty6Ciz/evcJcq6JpaVJ2jTNO5Q==|}
  562. |> Base64.decode_exn
  563. and key = {|-----BEGIN PUBLIC KEY-----
  564. MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxaVct4ctlG5ssph8+Qiu
  565. cV3/mtoTGE3nyl+9eTUaIvqdgcedIvDrAqJ4Koj6JEFMTjHpJYBvWXWhR7QEuYZJ
  566. 3HUjcZozxh40zCOosu841uvoIGEDq75xHRKhFRC38RBo7PRhAjqqCYgfkt7AERmw
  567. JBA1BF2EAPI5i5mjo2IPZzAJdByweVZoo5Wo3Ki3utF3oPXZRnuHmtLbphdJxZTD
  568. 9MXfaZLG6sfuzv+Og7O6AninawBmYFz60d8GwfaBOKi8ftbnheh04BGir+jgE02m
  569. WNgdMWOBM9D0D3+kmZ2+m+rACQCT9D500EtMWZSn4ZBDQxUkLLbXrZJt5/fDRur7
  570. QQIDAQAB
  571. -----END PUBLIC KEY-----
  572. |}
  573. |> X509.Public_key.decode_pem |> Result.get_ok
  574. and data = [ {|(request-target): post /2024-03-19/seppo.cgi/activitypub/inbox.jsa|} ;
  575. {|host: mx250.darknet.mro.name|} ;
  576. {|date: Wed, 02 Oct 2024 22:14:43 GMT|} ;
  577. {|digest: SHA-256=DpWuW0JAXqss/tShNxYG7NmD+o18Ok7lNDvHRD0vbcU=|} ]
  578. in
  579. key |> X509.Public_key.key_type |> X509.Key_type.to_string |> check string __LOC__ "rsa";
  580. X509.Public_key.verify
  581. `SHA256
  582. ~scheme:`RSA_PKCS1
  583. ~signature
  584. key
  585. (`Message (data |> String.concat "\n"))
  586. |> Result.get_ok
  587. (* also
  588. https://datatracker.ietf.org/doc/id/draft-richanna-http-message-signatures-00.html#example-key-rsa-test
  589. *)
  590. let pub_key_richanna =
  591. {|-----BEGIN PUBLIC KEY-----
  592. MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhAKYdtoeoy8zcAcR874L
  593. 8cnZxKzAGwd7v36APp7Pv6Q2jdsPBRrwWEBnez6d0UDKDwGbc6nxfEXAy5mbhgaj
  594. zrw3MOEt8uA5txSKobBpKDeBLOsdJKFqMGmXCQvEG7YemcxDTRPxAleIAgYYRjTS
  595. d/QBwVW9OwNFhekro3RtlinV0a75jfZgkne/YiktSvLG34lw2zqXBDTC5NHROUqG
  596. TlML4PlNZS5Ri2U4aCNx2rUPRcKIlE0PuKxI4T+HIaFpv8+rdV6eUgOrB2xeI1dS
  597. FFn/nnv5OoZJEIB+VmuKn3DCUcCZSFlQPSXSfBDiUGhwOw76WuSSsf1D4b/vLoJ1
  598. 0wIDAQAB
  599. -----END PUBLIC KEY-----
  600. |}
  601. |> X509.Public_key.decode_pem |> Result.get_ok
  602. (* example from
  603. https://datatracker.ietf.org/doc/id/draft-richanna-http-message-signatures-00.html#name-hs2019-signature-over-minim
  604. *)
  605. let tc_verify_hs2019_richanna_minimal () =
  606. let signature = "e3y37nxAoeuXw2KbaIxE2d9jpE7Z9okgizg6QbD2Z7fUVUvog+ZTKKLRBnhNglVIY6fAaYlHwx7ZAXXdBVF8gjWBPL6U9zRrB4PFzjoLSxHaqsvS0ZK9FRxpenptgukaVQ1aeva3PE1aD6zZ93df2lFIFXGDefYCQ+M/SrDGQOFvaVykEkte5mO6zQZ/HpokjMKvilfSMJS+vbvC1GJItQpjs636Db+7zB2W1BurkGxtQdCLDXuIDg4S8pPSDihkch/dUzL2BpML3PXGKVXwHOUkVG6Q2ge07IYdzya6N1fIVA9eKI1Y47HT35QliVAxZgE0EZLo8mxq19ReIVvuFg=="
  607. |> Base64.decode_exn
  608. and key = pub_key_richanna
  609. and data = [ {|(created): 1402170695|} ;
  610. {|(request-target): post /foo?param=value&pet=dog|} ]
  611. in
  612. key |> X509.Public_key.key_type |> X509.Key_type.to_string |> check string __LOC__ "rsa";
  613. X509.Public_key.verify
  614. `SHA256
  615. ~scheme:`RSA_PKCS1
  616. ~signature
  617. key
  618. (`Message (data |> String.concat "\n"))
  619. |> Result.get_ok
  620. (* example from
  621. https://datatracker.ietf.org/doc/id/draft-richanna-http-message-signatures-00.html#name-create-the-signature-input
  622. *)
  623. let tc_verify_hs2019_richanna_nonn () =
  624. let signature = {|T1l3tWH2cSP31nfuvc3nVaHQ6IAu9YLEXg2pCeEOJETXnlWbgKtBTaXV6LNQWtf4O42V2DZwDZbmVZ8xW3TFW80RrfrY0+fyjD4OLN7/zV6L6d2v7uBpuWZ8QzKuHYFaRNVXgFBXN3VJnsIOUjv20pqZMKO3phLCKX2/zQzJLCBQvF/5UKtnJiMp1ACNhG8LF0Q0FPWfe86YZBBxqrQr5WfjMu0LOO52ZAxi9KTWSlceJ2U361gDb7S5Deub8MaDrjUEpluphQeo8xyvHBoNXsqeax/WaHyRYOgaW6krxEGVaBQAfA2czYZhEA05Tb38ahq/gwDQ1bagd9rGnCHtAg==|}
  625. |> Base64.decode_exn
  626. and key = pub_key_richanna
  627. and data = {|(request-target): get /foo
  628. (created): 1402170695
  629. host: example.org
  630. date: Tue, 07 Jun 2014 20:51:35 GMT
  631. cache-control: max-age=60, must-revalidate
  632. x-emptyheader:
  633. x-example: Example header with some whitespace.|}
  634. in
  635. key |> X509.Public_key.key_type |> X509.Key_type.to_string |> check string __LOC__ "rsa";
  636. X509.Public_key.verify
  637. `SHA256
  638. ~scheme:`RSA_PKCS1
  639. ~signature
  640. key
  641. (`Message data)
  642. |> Result.get_ok
  643. (* example from
  644. https://gts.superseriousbusiness.org/@dumpsterqueer/statuses/01J9BKYKR1W5X078ZESSAW10QS
  645. *)
  646. let tc_verify_hs2019_gotosocial () =
  647. let signature = {|G6X7IV+qHqOaZIrrwRxunzbRgRhzn84UoUJfsSNLveHdVBAiaY3ayoj2F4ZiDxVV6zG0CN3j+0pHbngWgHp4aMETkF/x3KB/l2ILHgBhUpIB+ZAb1MkC+yU+9BNmp8EmVZldzdjQ/MalStfeRc7rcMdL770TJbAW8cgPRPA6TB7P6m5tzEPkow56wR/W0MuYJqWQzE8id7Ri65p63fu8NFha7WgBVM5I+67hZ3sYZTBKdLQJJyS4K3nOZ20h+pUSZUGF7WdTNnxtzaryJgFVL4Or2ydBa/Jp0w8zspWFMlGCtG9A9cayQ7JHlCMiuf92f/hpLWtWCSftg9IzZVakiw==|}
  648. |> Base64.decode_exn
  649. and key =
  650. "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy2/j9+2G7xrQvBtygrj4\naYHl8hTeZecDBnS+6IBjjEt+QWJ3z0Cv9lXSVMZw5i6DVTkVOGZrh2vZDu0BCTEV\n07dyASArE63Qe21WwjNObpkQ7YZbMxUkYjWCDYdqLMifAqElYzIK7xnY0pTWylmC\njm39qxmhk22PpzRkw+zofh9ykqyadmkA2/KrpZgGnjn6MiPqb2DeELV1tzmZ7mAz\n+k7pkkhxvBVqPhCZ104pyd1lc66obONSnIqxugRlrrUZbFv1e6xFsUmMUYrAGQTt\nZr4VeZwuaYj/MvFIeOZrmth/lg3QpYbKZYnJKVePyH+530jRSFerr2unbuGmEQAg\nvQIDAQAB\n-----END PUBLIC KEY-----\n"
  651. |> X509.Public_key.decode_pem |> Result.get_ok
  652. and data = {|(request-target): get /@mro/113244470620401979
  653. host: digitalcourage.social
  654. date: Fri, 04 Oct 2024 11:12:53 GMT|}
  655. in
  656. key |> X509.Public_key.key_type |> X509.Key_type.to_string |> check string __LOC__ "rsa";
  657. let `Msg m = X509.Public_key.verify
  658. `SHA256
  659. ~scheme:`RSA_PKCS1
  660. ~signature
  661. key
  662. (`Message data)
  663. |> Result.get_error in
  664. m
  665. |> check string __LOC__ "bad signature"
  666. let tc_verify_masto430 () =
  667. (*
  668. Signature: keyId="https://bewegung.social/use
  669. rs/mro#main-key",algorithm="rsa-sha256",headers="(request-target) host date digest content-type",signature="v2h3HUJg0vH6HyNfOHSE7hIg0O
  670. i1E+iUEO8ahxDjI99F7jKuh9sVRSpqhsAEagI5WeWZkQyhWjRmDZZBsk4+acALo36CsRj/C/m5CiF4J0hd+x8VSPWDJEQTYclm0jCthfdmeXg1/DOZnlWInBVQdwZJZyoW7nTn
  671. EEGZuE0w6LsYCJb2oVUTW32gn+fbHnJ2mkBwPjwBlJ7zckEx3MwnV99GPkjdA0hBX/O4xSD7a0MIn4d0CSOGmbnTGKChTm//AvKXP4L5H9L6ovZFBfaHkDCqYDbdfXGWeheLXd
  672. gDJHubi0LFecP3PP5cwOeuFtGgkWsSeLrUEyWgSlEKjCFRhw=="
  673. (request-target): post /2024-03-19/seppo.cgi/activitypub/inbox.jsa
  674. (request-target): post /2024-03-19/seppo.cgi/activitypub/inbox.jsa
  675. host: mx250.darknet.mro.name
  676. date: Thu, 10 Oct 2024 10:12:10 GMT
  677. digest: SHA-256=N0m+gyYe/GieNBzMEOStVqf7hq/qdmh7bqdWanZSE1o=
  678. content-type: application/activity+json
  679. *)
  680. ()
  681. let tc_sign_api () =
  682. (*
  683. sign != verify
  684. but has overlap
  685. constructing the signagee payload
  686. - keys
  687. - values
  688. -
  689. signing ig
  690. verifying it
  691. *)
  692. (** @TODO *)
  693. let _list_header_get l (k : string) : string option = l |> List.assoc_opt k in
  694. let compute_verify_signature_payload fetcher : string =
  695. (match fetcher "signature" with
  696. | None -> Some "header not found: signature"
  697. | Some si ->
  698. match si |> Http.Signature.decode with
  699. | Error _ -> Some "error decoding signature"
  700. | Ok si ->
  701. match si |> List.assoc_opt "headers" with
  702. | None -> Some "signature field not found: headers"
  703. | Some v ->
  704. (* assert lowercase *)
  705. v
  706. |> Astring.String.cuts ~sep:" " |> List.rev |> List.fold_left
  707. (fun init k -> match k |> fetcher with
  708. | None -> init
  709. | Some v -> (k,v) :: init) []
  710. |> Cohttp.Header.of_list
  711. |> Cohttp.Header.to_frames
  712. |> Astring.String.concat ~sep:"\n"
  713. |> Option.some )
  714. |> Option.value ~default:""
  715. in
  716. let r = {Cgi.Request.empty with
  717. request_method = "POST";
  718. raw_string = (function
  719. | "HTTP_HOST" -> Some "example.com"
  720. | "HTTP_DATE" -> Some "tomorrow"
  721. | "HTTP_SIGNATURE" -> Some {|keyId="Test",algorithm="rsa-sha256",headers="(request-target) host date",signature="qdx+H7PHHDZgy4y/Ahn9Tny9V3GP6YgBPyUXMmoxWtLbHpUnXS2mg2+SbrQDMCJypxBLSPQR2aAjn7ndmw2iicw3HMbe8VfEdKFYRqzic+efkb3nndiv/x1xSHDJWeSWkx3ButlYSuBskLu6kd9Fswtemr3lgdDEmn04swr2Os0="|}
  722. | _ -> None)} in
  723. Cgi.Request.header_get r "SIGNATURE" |> Option.get
  724. |> Http.Signature.decode |> Result.get_ok
  725. |> List.assoc "headers"
  726. |> Astring.String.cuts ~sep:" "
  727. |> List.length
  728. |> Assrt.equals_int __LOC__ 3;
  729. (* typical for verify *)
  730. r
  731. |> Cgi.Request.header_get
  732. |> compute_verify_signature_payload
  733. |> Assrt.equals_string __LOC__ {|(request-target): post /
  734. host: example.com
  735. date: tomorrow|};
  736. ();
  737. ()
  738. end
  739. let () =
  740. run
  741. "seppo_suite" [
  742. __FILE__ , [
  743. "setup", `Quick, set_up;
  744. "tc_relpa", `Quick, tc_relpa;
  745. "tc_uri", `Quick, tc_uri;
  746. "tc_rel_cd", `Quick, tc_rel_cd;
  747. "tc_rx_script_name", `Quick, Request.tc_rx_script_name;
  748. "tc_uri", `Quick, Request.tc_uri;
  749. "tc_base", `Quick, Request.tc_base;
  750. "tc_query_string", `Quick, Request.tc_query_string;
  751. "tc_rfc1123", `Quick, Cookie.tc_rfc1123;
  752. "tc_to_string", `Quick, Cookie.tc_to_string;
  753. "tc_of_string", `Quick, Cookie.tc_of_string;
  754. "tc_headers", `Quick, Header.tc_headers;
  755. "tc_sig_encode", `Quick, Header.tc_sig_encode;
  756. "tc_signature", `Quick, Header.tc_signature;
  757. "tc_sign2", `Quick, Header.tc_sign2;
  758. "tc_to_sign_string_basic", `Quick, Header.tc_to_sign_string_basic;
  759. "tc_sign_basic", `Quick, Header.tc_sign_basic;
  760. "tc_sign_all_headers", `Quick, Header.tc_sign_all_headers;
  761. "tc_signed_headers", `Quick, Header.tc_signed_headers;
  762. "tc_parse_auth_params", `Quick, Header.tc_parse_auth_params;
  763. "tc_parse_signature", `Quick, Header.tc_parse_signature;
  764. "tc_verify_basic", `Quick, Header.tc_verify_basic;
  765. "tc_verify_hs2019_raw", `Quick, Header.tc_verify_hs2019_raw;
  766. "tc_verify_hs2019", `Quick, Header.tc_verify_hs2019;
  767. "tc_verify_hs2019_b", `Quick, Header.tc_verify_hs2019_b;
  768. "tc_verify_hs2019_richanna_minimal", `Quick, Header.tc_verify_hs2019_richanna_minimal;
  769. "tc_verify_hs2019_richanna_nonn", `Quick, Header.tc_verify_hs2019_richanna_nonn;
  770. "tc_verify_hs2019_gotosocial", `Quick, Header.tc_verify_hs2019_gotosocial;
  771. "tc_verify_masto430", `Quick, Header.tc_verify_masto430;
  772. "tc_sign_api", `Quick, Header.tc_sign_api;
  773. ]
  774. ]