packages feed

hpke-0.4.0: Crypto/HPKE/Context.hs

{-# LANGUAGE RecordWildCards #-}

module Crypto.HPKE.Context (
    -- * Sender
    ContextS,
    newContextS,
    seal,
    exportS,

    -- * Receiver
    ContextR,
    newContextR,
    open,
    exportR,
) where

import Control.Concurrent.MVar (MVar, modifyMVar, newMVar)
import qualified Control.Exception as E
import Data.ByteArray (xor)
import qualified Data.ByteString as BS
import Data.IORef (IORef, atomicModifyIORef', newIORef)

import Crypto.HPKE.Types

----------------------------------------------------------------

-- | Context for senders.
data ContextS = ContextS
    { seqRefS :: IORef Integer
    , sealS :: Seal
    , nonceBaseS :: Nonce
    , expandS :: Info -> Int -> Either HPKEError Key
    }

-- | Context for receivers.
data ContextR = ContextR
    { seqVarR :: MVar Integer
    , openR :: Open
    , nonceBaseR :: Nonce
    , expandR :: Info -> Int -> Either HPKEError Key
    }

----------------------------------------------------------------

-- | Encryption.
--   This throws 'HPKEError'.
--   Safe to call from multiple threads: each call gets its own nonce.
seal :: ContextS -> AAD -> PlainText -> IO CipherText
seal ContextS{..} aad pt = do
    seqI <- atomicModifyIORef' seqRefS $ \i -> (i + 1, i)
    checkLimit nonceBaseS seqI
    either E.throwIO return $ sealS (computeNonce nonceBaseS seqI) aad pt

-- | Decryption.
--   This throws 'HPKEError'.
--   The sequence number advances only when decryption succeeds.
open
    :: ContextR -> AAD -> CipherText -> IO PlainText
open ContextR{..} aad ct = modifyMVar seqVarR $ \seqI -> do
    checkLimit nonceBaseR seqI
    case openR (computeNonce nonceBaseR seqI) aad ct of
        Left err -> E.throwIO err
        Right pt -> return (seqI + 1, pt)

computeNonce :: Nonce -> Integer -> Nonce
computeNonce nonce_base seqI = seqBS `xor` nonce_base
  where
    seqBS = i2ospOf_ (BS.length nonce_base) seqI :: ByteString

-- RFC 9180 section 5.2: the sequence number must not wrap around.
checkLimit :: Nonce -> Integer -> IO ()
checkLimit nonce_base seqI
    | seqI >= 2 ^ (8 * BS.length nonce_base) - 1 =
        E.throwIO $ MessageLimitReachedError "sequence number overflow"
    | otherwise = return ()

----------------------------------------------------------------

-- | Exporting secret.
--
-- RFC 9180 section 5.3 allows a length of at most @255 * Nh@, where @Nh@ is
-- the output of the KDF's hash; a longer one is 'Left' 'ExportError'.
exportS :: ContextS -> Info -> Int -> Either HPKEError Key
exportS ContextS{..} exporter_context len =
    expandS exporter_context len

-- | Exporting secret.
--
-- RFC 9180 section 5.3 allows a length of at most @255 * Nh@, where @Nh@ is
-- the output of the KDF's hash; a longer one is 'Left' 'ExportError'.
exportR :: ContextR -> Info -> Int -> Either HPKEError Key
exportR ContextR{..} exporter_context len =
    expandR exporter_context len

----------------------------------------------------------------

newContextS
    :: Key
    -> Nonce
    -> (Key -> Seal)
    -> (Info -> Int -> Either HPKEError Key)
    -> IO ContextS
newContextS key nonce_base seal' expand = do
    seqref <- newIORef 0
    return $
        ContextS
            { seqRefS = seqref
            , sealS = seal' key
            , nonceBaseS = nonce_base
            , expandS = expand
            }

----------------------------------------------------------------

newContextR
    :: Key
    -> Nonce
    -> (Key -> Open)
    -> (Info -> Int -> Either HPKEError Key)
    -> IO ContextR
newContextR key nonce_base open' expand = do
    seqvar <- newMVar 0
    return $
        ContextR
            { seqVarR = seqvar
            , openR = open' key
            , nonceBaseR = nonce_base
            , expandR = expand
            }