123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154(* This file is part of Dream, released under the MIT license. See LICENSE.md
for details, or visit https://github.com/aantron/dream.
Copyright 2021 Anton Bachin *)(* TODO Review all | exception cases in all code and avoid them as much sa
possible. *)(* TODO Support mixture of encryption and signing. *)(* TODO LATER Switch to AEAD_AES_256_GCM_SIV. See
https://github.com/mirage/mirage-crypto/issues/111. *)moduleMessage=Dream_pure.MessagemoduletypeCipher=sigvalprefix:charvalname:stringvalencrypt:?associated_data:string->secret:string->string->stringvaldecrypt:?associated_data:string->secret:string->string->stringoptionvaltest_encrypt:?associated_data:string->secret:string->nonce:string->string->stringendletencrypt(moduleCipher:Cipher)?associated_datasecretplaintext=Cipher.encrypt?associated_data~secretplaintextletrecdecrypt((moduleCipher:Cipher)ascipher)?associated_datasecretsciphertext=matchsecretswith|[]->None|secret::secrets->matchCipher.decrypt?associated_data~secretciphertextwith|Some_asplaintext->plaintext|None->decryptciphersecretsciphertext(* Key is good for ~2.5 years if every request e.g. generates one new signed
cookie, and the installation is doing 1000 requests per second. *)moduleAEAD_AES_256_GCM=struct(* Enciphered messages are prefixed with a version. There is only one right
now, version 0, in which the rest of the message consists of:
- a 96-bit nonce, as recommended in RFC 5116.
- ciphertext generated by AEAD_AES_256_GCM (RFC 5116).
The 256-bit key is "derived" from the given secret by hashing it with
SHA-256.
See https://tools.ietf.org/html/rfc5116. *)(* TODO Move this check to the envelope loop. *)letprefix='\x00'letname="AEAD_AES_256_GCM, "^"mirage-crypto, key: SHA-256, nonce: 96 bits mirage-crypto-rng"letderive_keysecret=secret|>Digestif.SHA256.digest_string|>Digestif.SHA256.to_raw_string|>Mirage_crypto.AES.GCM.of_secret(* TODO Memoize keys or otherwise avoid key derivation on every call. *)letencrypt_with_noncesecretnonceplaintextassociated_data=letkey=derive_keysecretinletadata=associated_datainletciphertext=Mirage_crypto.AES.GCM.authenticate_encrypt~key~nonce?adataplaintextin"\x00"^nonce^ciphertextletencrypt?associated_data~secretplaintext=encrypt_with_noncesecret(Random.random_buffer12)plaintextassociated_datalettest_encrypt?associated_data~secret~nonceplaintext=encrypt_with_noncesecretnonceplaintextassociated_dataletdecrypt?associated_data~secretciphertext=letkey=derive_keysecretinifString.lengthciphertext<14thenNoneelseifciphertext.[0]!=prefixthenNoneelseletadata=associated_datainletplaintext=Mirage_crypto.AES.GCM.authenticate_decrypt~key~nonce:(String.subciphertext112)?adata(String.subciphertext13(String.lengthciphertext-13))inplaintextendletsecrets_field=Message.new_field~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. *)letset_secret?(old_secrets=[])secret=letvalue=secret::old_secretsinfunnext_handlerrequest->Message.set_fieldrequestsecrets_fieldvalue;next_handlerrequestletfallback_secrets=lazy[Random.random32]letencryption_secretrequest=matchMessage.fieldrequestsecrets_fieldwith|Somesecrets->List.hdsecrets|None->List.hd(Lazy.forcefallback_secrets)letdecryption_secretsrequest=matchMessage.fieldrequestsecrets_fieldwith|Somesecrets->secrets|None->Lazy.forcefallback_secretsletencrypt?associated_datarequestplaintext=encrypt(moduleAEAD_AES_256_GCM)?associated_data(encryption_secretrequest)plaintextletdecrypt?associated_datarequestciphertext=decrypt(moduleAEAD_AES_256_GCM)?associated_data(decryption_secretsrequest)ciphertext