{-# OPTIONS_GHC -Wno-redundant-constraints #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}

-- SPDX-FileCopyrightText: 2020 Serokell
--
-- SPDX-License-Identifier: MPL-2.0

-- | This module gives different ways of obtaining secret keys.
--
-- = Key derivation (from a password)
--
-- Sometimes, instead of generating fresh random transient encryption keys,
-- you want an encryption key to be persistent and you don’t want to
-- store it anywhere – instead you ask the user to provide it.
-- Such a secret value known and entered by the user is usually called
-- a “password”.
--
-- However, passwords make terrible encryption keys because encryption keys:
--
-- 1. often need to have a specific exact length and
-- 2. need to be hard to guess (or brute-force).
--
-- Item 1 above can be easily ticked off by deriving the encryption key from
-- the password by applying a hash-function to it, however in order to
-- achieve 2 we need our “hash-function” to:
--
-- * be slow to compute (to make brute-forcing less feasible) and
-- * mix extra “noise” into the derivation process to make it harder
--   to pre-compute derived values in advance.
--
-- A construction that satisfies both requirements is called a
-- /key derivation function (KDF)/.
-- This module provides a convenient interface for deriving
-- secure keys from passwords by the way of one such KDF.
--
-- == Use
--
-- This module provides two functions: 'derive' and 'rederive'.
-- You can think of the entire process similar to how you set the password
-- on your account at some website once, and then use this password to log in.
--
-- When you derive a key for the first time (e.g. you ask the user to enter their
-- password twice, and then encrypt something), you use the 'derive' function,
-- which gives you the derived key and a /derivation slip/. The slip is not
-- secret, you can store it in plain text and, in fact, you /have to/ store it
-- in plaintext somewhere next to the encrypted data.
--
-- When you need to derive the key in the future (e.g. to decrypt some previously
-- encrypted data), you will need the user’s password (ask them) /and/ you
-- will need the original derivation slip, which you should have stored.
-- You pass these to 'rederive' and it will give you the same key.
--
-- @
-- import qualified Crypto.Sodium.Key as Key
--
-- encrypt = do
--   password <- {- ask the user to enter their password -}
--   password2 <- {- ask the user to confirm their password -}
--   when (password /= password2 then) $ throwIO {- passwords do not match -}
--
--   let params = {- choose key derivation parameters -}
--   (key, slip) <- Key.derive params password
--
--   {- store slip (it is not secret) -}
--   {- encrypt data with key -}
--
-- decrypt = do
--   password <- {- ask the user to enter their password -}
--   slip <- {- get the stored slip -}
--
--   key <- Key.rederive slip password
--
--   {- decrypt data with key -}
-- @
--
-- = Random key generation
--
-- The 'generate' function is great at generating new secure secret keys.
module Crypto.Sodium.Key
  ( type (!>=!)

  -- * Key derivation
  , Params (..)
  , DerivationSlip
  , derive
  , rederive

  -- * Random key generation
  , generate
  ) where

import Data.ByteArray (ByteArrayAccess, ScrubbedBytes)
import Data.ByteArray.Sized (ByteArrayN, SizedByteArray)
import Data.Kind (Constraint)
import GHC.TypeLits (type (<=), KnownNat)
import System.IO.Unsafe (unsafePerformIO)

import qualified Libsodium as Na

import Crypto.Sodium.Key.Internal (DerivationSlip, Params (..))

import qualified Crypto.Sodium.Key.Internal as I
import qualified Crypto.Sodium.Random


-- | “At least as secure as”.
--
-- @a !>=! b@ means that the storage behind a is not less secure than b.
-- This is a little bit of an ad-hoc safety hack, which ensures that if
-- @b@ is stored in a securely allocated memory, then @a@ is stored in
-- memory allocated as securely, or more securely.
--
-- Here are our very ad-hoc rules:
--
-- * This relation is reflexive (@a@ is as secure as @a@ for any @a@).
-- * 'ScrubbedBytes' is more secure than anything.
-- * Everything else is equally (in)secure.
--
-- So, for example, if the original password is stored in @ScrubbedBytes@,
-- you will not be able to put the derived from it key into a @ByteString@,
-- because that would be less secure.
type family a !>=! b :: Constraint where
  a !>=! a = ()  -- reflexivity
  a !>=! ScrubbedBytes = LessSecureStorage a ScrubbedBytes
  a !>=! b = ()
class LessSecureStorage a b


-- | Derive a key from a password using a secure KDF for the first time.
--
-- This function takes two arguments:
--
-- 1. key derivation parameters, which specify how slow the derivation process
-- will be (the slower you can afford the better for security),
--
-- 2. the user’s password to derive the key from.
--
-- See @libsodium@ documentation for how to determine 'Params'.
--
-- It returns the derived key and a /slip/ that you need to save in order to be
-- able to derive the same key from the same password in the future. The slip
-- is not secret, so you can store it in plaintext; just make sure you can
-- access it in the future, as you will need to provide it to 'rederive'.
--
-- It can derive a key of almost any length and the output length is encoded
-- in the type. There is an additional type-level restriction which forces
-- you to store the derived key in memory at least as securely as you
-- stored the password.
--
-- Note: This function is not thread-safe until Sodium is initialised.
-- See "Crypto.Sodium.Init" for details.
derive
  ::  forall key n passwd.
      ( ByteArrayAccess passwd
      , ByteArrayN n key, key !>=! passwd
      , Na.CRYPTO_PWHASH_BYTES_MIN <= n, n <= Na.CRYPTO_PWHASH_BYTES_MAX
      )
  => I.Params -- ^ Derivation parameters.
  -> passwd  -- ^ Password to derive from.
  -> IO (Maybe (key, I.DerivationSlip))
derive :: Params -> passwd -> IO (Maybe (key, DerivationSlip))
derive = Params -> passwd -> IO (Maybe (key, DerivationSlip))
forall passwd (n :: Nat) key.
(ByteArrayAccess passwd, ByteArrayN n key,
 CRYPTO_PWHASH_BYTES_MIN <= n, n <= CRYPTO_PWHASH_BYTES_MAX) =>
Params -> passwd -> IO (Maybe (key, DerivationSlip))
I.derive

-- | Reerive a key from a password using a secure KDF.
--
-- This function takes two arguments:
--
-- 1. A derivation slip previously returned by 'derive'.
--
-- 2. The user’s password.
--
-- This function is guaranteed to derive the same key from the same password
-- as long as the same derivation slip was provided.
--
-- See 'derive' for additional details.
rederive
  ::  forall key n passwd.
      ( ByteArrayAccess passwd
      , ByteArrayN n key, key !>=! passwd
      , Na.CRYPTO_PWHASH_BYTES_MIN <= n, n <= Na.CRYPTO_PWHASH_BYTES_MAX
      )
  => I.DerivationSlip -- ^ Original derivation slip.
  -> passwd  -- ^ Password to rederive from.
  -> Maybe key
rederive :: DerivationSlip -> passwd -> Maybe key
rederive DerivationSlip
slip passwd
passwd =
  IO (Maybe key) -> Maybe key
forall a. IO a -> a
unsafePerformIO (IO (Maybe key) -> Maybe key) -> IO (Maybe key) -> Maybe key
forall a b. (a -> b) -> a -> b
$ DerivationSlip -> passwd -> IO (Maybe key)
forall passwd (n :: Nat) key.
(ByteArrayAccess passwd, ByteArrayN n key,
 CRYPTO_PWHASH_BYTES_MIN <= n, n <= CRYPTO_PWHASH_BYTES_MAX) =>
DerivationSlip -> passwd -> IO (Maybe key)
I.rederive DerivationSlip
slip passwd
passwd
  -- This IO is safe, because it is pure.


-- | Generate a new secret key using a cryptographically-secure generator.
--
-- This is just a specialisation of @Crypto.Sodium.Random.'generate'@ that stores
-- it in a secure memory location.
--
-- Note: This function is not thread-safe until Sodium is initialised.
-- See "Crypto.Sodium.Init" for details.
generate :: KnownNat n => IO (SizedByteArray n ScrubbedBytes)
generate :: IO (SizedByteArray n ScrubbedBytes)
generate = IO (SizedByteArray n ScrubbedBytes)
forall ba (n :: Nat).
(ByteArray ba, KnownNat n) =>
IO (SizedByteArray n ba)
Crypto.Sodium.Random.generate