cookie.ml 4.9 KB

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