cookie.ml 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. (*
  2. * _ _ ____ _
  3. * _| || |_/ ___| ___ _ __ _ __ ___ | |
  4. * |_ .. _\___ \ / _ \ '_ \| '_ \ / _ \| |
  5. * |_ _|___) | __/ |_) | |_) | (_) |_|
  6. * |_||_| |____/ \___| .__/| .__/ \___/(_)
  7. * |_| |_|
  8. *
  9. * Personal Social Web.
  10. *
  11. * cookie.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 Astring
  29. (* TODO maybe make compatible with Cohttp.Cookie *)
  30. (* https://opam.ocaml.org/packages/http-cookie/
  31. * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie *)
  32. (* figure out the session cookie / authentication *)
  33. (* http://pleac.sourceforge.net/pleac_ocaml/cgiprogramming.html *)
  34. (* https://github.com/aantron/dream/blob/master/src/server/cookie.ml *)
  35. (* https://aantron.github.io/dream/#cookies *)
  36. (* https://aantron.github.io/dream/#val-from_cookie
  37. and
  38. https://aantron.github.io/dream/#val-to_set_cookie
  39. *)
  40. (* encrypt & decrypt
  41. https://github.com/aantron/dream/blob/181175d3a9e12c145033728b98a091e38e8501f6/src/cipher/cipher.ml
  42. https://github.com/aantron/dream/blob/master/src/cipher/cipher.ml#L92
  43. *)
  44. (* https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-4.2.1 *)
  45. let of_string s : Cohttp.Cookie.cookie list =
  46. let sep = Char.equal in
  47. (* https://github.com/aantron/dream/blob/master/src/pure/formats.ml#L32 *)
  48. s
  49. |> String.fields ~is_sep:(sep ';')
  50. |> List.map (String.fields ~is_sep:(sep '='))
  51. |> List.fold_left (fun pairs -> function
  52. | [] -> pairs
  53. | [name] -> (String.trim name, "") :: pairs
  54. | [name; value] -> (String.trim name, String.trim value) :: pairs
  55. | _ -> assert false) []
  56. (* https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-07#section-4.2.1
  57. * https://github.com/aantron/dream/blob/master/src/pure/formats.ml#L51
  58. *
  59. * Cohttp seems to not set SameSite, so we maintain our own.
  60. *)
  61. let to_string ?expires ?max_age ?domain ?path ?secure ?http_only ?same_site
  62. ((name, value) : Cohttp.Cookie.cookie) =
  63. (* MIT License, Copyright 2021 Anton Bachin, 2022 Marcus Rohrmoser
  64. https://github.com/aantron/dream/blob/master/src/pure/formats.ml#L51 *)
  65. let expires =
  66. (* empty = session cookie. RFC2616, RFC1123 *)
  67. match Option.bind expires Ptime.of_float_s with
  68. | None -> ""
  69. | Some time -> "; Expires=" ^ Http.to_rfc1123 time
  70. and max_age =
  71. (* supposed to replace expires? *)
  72. match max_age with
  73. | None -> ""
  74. | Some seconds -> Printf.sprintf "; Max-Age=%.0f" seconds
  75. and domain =
  76. match domain with
  77. | None -> ""
  78. | Some domain -> Printf.sprintf "; Domain=%s" domain
  79. and path =
  80. match path with
  81. | None -> ""
  82. | Some path -> Printf.sprintf "; Path=%s" path
  83. and secure = match secure with Some true -> "; Secure" | _ -> ""
  84. and http_only = match http_only with Some true -> "; HttpOnly" | _ -> ""
  85. and same_site =
  86. match same_site with
  87. | None -> ""
  88. | Some `Strict -> "; SameSite=Strict"
  89. | Some `Lax -> "; SameSite=Lax"
  90. | Some `None -> "; SameSite=None"
  91. in
  92. Printf.sprintf "%s=%s%s%s%s%s%s%s%s" name value expires max_age domain path
  93. secure http_only same_site
  94. let l12 = 12
  95. let random_nonce () =
  96. (* https://github.com/lemaetech/summer/blob/main/lib/summer.ml#L510-L520 *)
  97. Mirage_crypto_rng.generate l12
  98. let encrypt sec nonce adata =
  99. (* https://github.com/lemaetech/summer/blob/main/lib/summer.ml#L510-L520 *)
  100. assert (32 = (sec |> Cstruct.length));
  101. assert (l12 = (nonce |> Cstruct.length));
  102. let key = sec |> Mirage_crypto.Chacha20.of_secret in
  103. adata
  104. |> Mirage_crypto.Chacha20.authenticate_encrypt ~key ~nonce
  105. |> Cstruct.append nonce
  106. |> Cstruct.to_string
  107. |> Base64.(encode_string ~pad:false ~alphabet:uri_safe_alphabet)
  108. let decrypt sec noadata =
  109. try
  110. assert (32 = (sec |> Cstruct.length));
  111. (* https://github.com/lemaetech/summer/blob/main/lib/summer.ml#L510-L520 *)
  112. let noadata = noadata
  113. |> Base64.(decode_exn ~pad:false ~alphabet:uri_safe_alphabet)
  114. |> Cstruct.of_string
  115. and key = sec |> Mirage_crypto.Chacha20.of_secret
  116. and len = 12 in
  117. let nonce = Cstruct.sub noadata 0 len in
  118. Option.bind
  119. (Cstruct.sub noadata len (Cstruct.length noadata - len)
  120. |> Mirage_crypto.Chacha20.authenticate_decrypt ~key ~nonce)
  121. (fun v -> Some (Cstruct.to_string v))
  122. with
  123. Invalid_argument _ -> None