Skip to content

Commit

Permalink
Set secrets using a middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
aantron committed Dec 13, 2021
1 parent d81b198 commit 2529b9a
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 55 deletions.
40 changes: 38 additions & 2 deletions src/cipher/cipher.ml
Expand Up @@ -11,6 +11,12 @@
(* TODO LATER Switch to AEAD_AES_256_GCM_SIV. See
https://github.com/mirage/mirage-crypto/issues/111. *)



module Dream = Dream_pure



module type Cipher =
sig
val prefix : char
Expand Down Expand Up @@ -112,16 +118,46 @@ struct
| Some plaintext -> Some (Cstruct.to_string plaintext)
end

let secrets =
Dream.new_local
~name:"dream.secret"
~show_value:(fun _secrets -> "[redacted]")
()

(* TODO Add warnings about secret length and such. *)
(* TODO Also add warnings about implicit secret generation. However, these
warnings might be pretty spammy. *)
(* TODO Update examples and docs. *)
let with_secret ?(old_secrets = []) secret =
let value = secret::old_secrets in
fun next_handler request ->
request
|> Dream.with_local secrets value
|> next_handler

let fallback_secrets =
lazy [Random.random 32]

let encryption_secret request =
match Dream.local secrets request with
| Some secrets -> List.hd secrets
| None -> List.hd (Lazy.force fallback_secrets)

let decryption_secrets request =
match Dream.local secrets request with
| Some secrets -> secrets
| None -> Lazy.force fallback_secrets

let encrypt ?associated_data request plaintext =
encrypt
(module AEAD_AES_256_GCM)
?associated_data
(Dream_pure.encryption_secret request)
(encryption_secret request)
plaintext

let decrypt ?associated_data request ciphertext =
decrypt
(module AEAD_AES_256_GCM)
?associated_data
(Dream_pure.decryption_secrets request)
(decryption_secrets request)
ciphertext
39 changes: 23 additions & 16 deletions src/dream.mli
Expand Up @@ -1973,8 +1973,6 @@ val run :
?stop:unit promise ->
?debug:bool ->
?error_handler:error_handler ->
?secret:string ->
?old_secrets:string list ->
?prefix:string ->
?https:bool ->
?certificate_file:string ->
Expand Down Expand Up @@ -2006,18 +2004,6 @@ val run :
low-level errors. See {!section-errors} and example
{{:https://github.com/aantron/dream/tree/master/example/9-error#files}
[9-error]} \[{{:http://dream.as/9-error} playground}\].
- [~secret] is a key to be used for cryptographic operations, such as
signing CSRF tokens. By default, a random secret is generated on each call
to {!Dream.run}. For production, generate a 256-bit key with
{[
Dream.to_base64url (Dream.random 32)
]}
and load it from file. A medium-sized Web app serving 1000 fresh encrypted
cookies per second should rotate keys about once a year. See argument
[~old_secrets] below for key rotation. See {!Dream.encrypt} for cipher
information.
- [~old_secrets] is a list of previous secrets that can still be used for
decryption, but not for encryption. This is intended for key rotation.
- [~prefix] is a site prefix for applications that are not running at the
root ([/]) of their domain. The default is ["/"], for no prefix.
- [~https:true] enables HTTPS. You should also specify [~certificate_file]
Expand Down Expand Up @@ -2050,8 +2036,6 @@ val serve :
?stop:unit promise ->
?debug:bool ->
?error_handler:error_handler ->
?secret:string ->
?old_secrets:string list ->
?prefix:string ->
?https:bool ->
?certificate_file:string ->
Expand Down Expand Up @@ -2239,6 +2223,29 @@ val application_json : string

(** {1 Cryptography} *)

val with_secret : ?old_secrets:string list -> string -> middleware
(** Sets a key to be used for cryptographic operations, such as signing CSRF
tokens and encrypting cookies.
If this middleware is not used, a random secret is generated the first time
a secret is needed. The random secret persists for the lifetime of the
process. This is useful for quick testing and prototyping, but it means that
restarts of the server will not be able to verify tokens or decrypt cookies
generated by earlier runs, and multiple servers in a load-balancing
arrangement will not accept each others' tokens and cookies.
For production, generate a 256-bit key with
{[
Dream.to_base64url (Dream.random 32)
]}
[~old_secrets] is a list of previous secrets that will not be used for
encryption or signing, but will still be tried for decryption and
verification. This is intended for key rotation. A medium-sized Web app
serving 1000 fresh encrypted cookies per second should rotate keys about
once a year. *)

val random : int -> string
(** Generates the requested number of bytes using a
{{:https://github.com/mirage/mirage-crypto} cryptographically secure random
Expand Down
19 changes: 3 additions & 16 deletions src/http/http.ml
Expand Up @@ -751,8 +751,6 @@ let serve_with_maybe_https
~stop
?debug
~error_handler
?(secret = Dream__cipher.Random.random 32)
?(old_secrets = [])
~prefix
~https
?certificate_file ?key_file
Expand All @@ -775,16 +773,13 @@ let serve_with_maybe_https

(* This check will at least catch secrets like "foo" when used on a public
interface. *)
if not (is_localhost interface) then
(* if not (is_localhost interface) then
if String.length secret < 32 then begin
log.warning (fun log -> log "Using a short key on a public interface");
log.warning (fun log ->
log "Consider using Dream.to_base64url (Dream.random 32)");
end;

(* TODO The interface needs to allow not messing with the secret if an app
is passed. *)
Dream.set_secrets (secret::old_secrets) app;
end; *)
(* TODO Make sure there is a similar check in cipher.ml now.Hpack *)

match https with
| `No ->
Expand Down Expand Up @@ -920,8 +915,6 @@ let serve
?(stop = never)
?debug
?(error_handler = Error_handler.default)
?secret
?old_secrets
?(prefix = "")
?(https = false)
?certificate_file
Expand All @@ -936,8 +929,6 @@ let serve
~stop
?debug
~error_handler
?secret
?old_secrets
~prefix
~https:(if https then `OpenSSL else `No)
?certificate_file
Expand All @@ -955,8 +946,6 @@ let run
?(stop = never)
?debug
?(error_handler = Error_handler.default)
?secret
?old_secrets
?(prefix = "")
?(https = false)
?certificate_file
Expand Down Expand Up @@ -1036,8 +1025,6 @@ let run
~stop
?debug
~error_handler
?secret
?old_secrets
~prefix
~https:(if https then `OpenSSL else `No)
?certificate_file ?key_file
Expand Down
6 changes: 0 additions & 6 deletions src/pure/dream_pure.mli
Expand Up @@ -140,11 +140,6 @@ val prefix : request -> string
val internal_prefix : request -> string list
val path : request -> string list
val version : request -> int * int
val encryption_secret : request -> string
val decryption_secrets : request -> string list
(* TODO Get the encryption secrets out of here and into the server only.
Also try to move the whole "app" mechanism to the server only. However, how
will that interact with in-process testing? *)
val site_prefix : request -> string list
(* TODO This will be moved out of dream-pure and become just a server-side
middleware.. *)
Expand Down Expand Up @@ -404,7 +399,6 @@ val app : request -> app
val debug : app -> bool
val set_debug : bool -> app -> unit
val app_error_handler : app -> (error -> response promise)
val set_secrets : string list -> app -> unit
val set_https : bool -> app -> unit
val request_from_http :
app:app ->
Expand Down
15 changes: 0 additions & 15 deletions src/pure/inmost.ml
Expand Up @@ -72,7 +72,6 @@ and server = {
and app = {
mutable app_debug : bool;
mutable https : bool;
mutable secrets : string list;
error_handler : error -> response Lwt.t;
site_prefix : string list;
}
Expand Down Expand Up @@ -127,13 +126,6 @@ let set_debug value app =
let app_error_handler app =
app.error_handler

(* TODO Delete; now using key. *)
let secret app =
List.hd app.secrets

let set_secrets secrets app =
app.secrets <- secrets

let set_https https app =
app.https <- https

Expand All @@ -143,7 +135,6 @@ let site_prefix request =
let new_app error_handler site_prefix = {
app_debug = false;
https = false;
secrets = [];
error_handler;
site_prefix;
}
Expand Down Expand Up @@ -595,12 +586,6 @@ let rec pipeline middlewares handler =
let sort_headers headers =
List.stable_sort (fun (name, _) (name', _) -> compare name name') headers

let encryption_secret request =
List.hd request.specific.app.secrets

let decryption_secrets request =
request.specific.app.secrets

(* TODO Remove to server-side code. *)
let multipart_state request =
request.specific.upload

0 comments on commit 2529b9a

Please sign in to comment.