packages feed

botan-low-0.1.0.0: src/Botan/Low/PubKey/KeyEncapsulation.hs

{-|
Module      : Botan.Low.KeyEncapsulation
Description : Key Encapsulation
Copyright   : (c) 2023-2024, Apotheca Labs
              (c) 2024-2025, Haskell Foundation
License     : BSD-3-Clause
Maintainer  : joris@well-typed.com, leo@apotheca.io
Stability   : experimental
Portability : POSIX
-}

module Botan.Low.PubKey.KeyEncapsulation (

  -- * PK Key Encapsulation
  -- $introduction
  -- * Usage
  -- $usage

  -- * KEM Encryption
    KEMSharedKey
  , KEMEncapsulatedKey
  , KEMEncrypt(..)
  , withKEMEncrypt
  , kemEncryptDestroy
  , kemEncryptCreate
  , kemEncryptSharedKeyLength
  , kemEncryptEncapsulatedKeyLength
  , kemEncryptCreateSharedKey

  -- * KEM Decryption
  , KEMDecrypt(..)
  , withKEMDecrypt
  , kemDecryptDestroy
  , kemDecryptCreate
  , kemDecryptSharedKeyLength
  , kemDecryptSharedKey

  ) where

import           Botan.Bindings.ConstPtr (ConstPtr (..))
import           Botan.Bindings.PubKey.KeyEncapsulation
import           Botan.Low.Error.Internal
import           Botan.Low.Internal.ByteString
import           Botan.Low.KDF
import           Botan.Low.Make
import           Botan.Low.PubKey
import           Botan.Low.Remake
import           Botan.Low.RNG
import           Control.DeepSeq
import           Data.ByteString (ByteString)
import qualified Data.ByteString as ByteString
import           Foreign.C.Types
import           Foreign.ForeignPtr
import           Foreign.Marshal.Alloc
import           Foreign.Ptr
import           Foreign.Storable

{- $introduction

Key encapsulation (KEM) is a variation on public key encryption which is
commonly used by post-quantum secure schemes. Instead of choosing a random
secret and encrypting it, as in typical public key encryption, a KEM encryption
takes no inputs and produces two values, the shared secret and the encapsulated
key. The decryption operation takes in the encapsulated key and returns the
shared secret.

-}

{- $usage

> NOTE: KEM only requires the public knowledge of one person's key pair, unlike
> Key Agreement.

First, Alice generates her private and public key pair:

> import Botan.Low.PubKey
> import Botan.Low.PubKey.KeyEncapsulation
> import Botan.Low.Hash
> import Botan.Low.KDF
> import Botan.Low.RNG
> rng <- rngInit UserRNG
> -- Alice generates her private and public keys
> alicePrivKey <- privKeyCreate RSA "2048" rng
> alicePubKey <- privKeyExportPubKey alicePrivKey

Then, Alice shares her public key somewhere where others can see. When Bob
wants to create a shared key with Alice, they choose a KDF algorithm, generate
a salt, and choose a shared key length.

> kdfAlg = hkdf SHA256
> salt <- rngGet rng 4
> sharedKeyLength = 256

Then, Bob generates the shared + encapsulated key, and sends the
encapsulated key to Alice:

> encryptCtx <- kemEncryptCreate alicePubKey kdfAlg
> (bobSharedKey, encapsulatedKey) <- kemEncryptCreateSharedKey encryptCtx rng salt sharedKeyLength
> -- sendToAlice encapsulatedKey

Upon receiving the encapsulated key, Alice can decrypt and extract the shared
key using her private key:

> decryptCtx <- kemDecryptCreate alicePrivKey kdfAlg
> aliceSharedKey <- kemDecryptSharedKey decryptCtx salt encapsulatedKey sharedKeyLength
> bobSharedKey == aliceSharedKey
> -- True

Then, this shared key may be used for any suitable purpose.

-}

-- TODO: KEM supports the following key types:
--      RSA
--      Kyber
--      McEliece
--  https://botan.randombit.net/handbook/api_ref/pubkey.html#key-encapsulation
-- KYBER is post-quantum

type KEMSharedKey = ByteString
type KEMEncapsulatedKey = ByteString

newtype KEMEncrypt = MkKEMEncrypt { getKEMEncryptForeignPtr :: ForeignPtr BotanPKOpKEMEncryptStruct }

withKEMEncrypt     :: KEMEncrypt -> (BotanPKOpKEMEncrypt -> IO a) -> IO a
kemEncryptDestroy  :: KEMEncrypt -> IO ()
createKEMEncrypt   :: (Ptr BotanPKOpKEMEncrypt -> IO CInt) -> IO KEMEncrypt
(withKEMEncrypt, kemEncryptDestroy, createKEMEncrypt)
    = mkBindings
        MkBotanPKOpKEMEncrypt (.runBotanPKOpKEMEncrypt)
        MkKEMEncrypt (.getKEMEncryptForeignPtr)
        botan_pk_op_kem_encrypt_destroy





kemEncryptCreate
    :: PubKey           -- ^ __key__
    -> KDFName          -- ^ __kdf__
    -> IO KEMEncrypt    -- ^ __op__
kemEncryptCreate pk algo = withPubKey pk $ \ pkPtr -> do
    asCString algo $ \ algoPtr -> do
        createKEMEncrypt $ \ out -> botan_pk_op_kem_encrypt_create
            out
            pkPtr
            (ConstPtr algoPtr)

kemEncryptSharedKeyLength
    :: KEMEncrypt   -- ^ __op__
    -> Int          -- ^ __desired_shared_key_length__
    -> IO Int       -- ^ __output_shared_key_length__
kemEncryptSharedKeyLength = mkGetSize_csize withKEMEncrypt botan_pk_op_kem_encrypt_shared_key_length


kemEncryptEncapsulatedKeyLength
    :: KEMEncrypt   -- ^ __op__
    -> IO Int       -- ^ __output_encapsulated_key_length__
kemEncryptEncapsulatedKeyLength = mkGetSize withKEMEncrypt botan_pk_op_kem_encrypt_encapsulated_key_length

-- NOTE: Awkward because of double-query and returning double bytestrings
--  Cannot use allocBytesQuerying because of double-return
-- NOTE: Returns (SharedKey, EncapsulatedKey)


kemEncryptCreateSharedKey
    :: KEMEncrypt                           -- ^ __op__
    -> RNG                                  -- ^ __rng__
    -> ByteString                           -- ^ __salt[]__
    -> Int                                  -- ^ __desired_shared_key_len__
    -> IO (KEMSharedKey,KEMEncapsulatedKey) -- ^ __(shared_key,encapsulated_key)__
kemEncryptCreateSharedKey ke rng salt desiredLen = withKEMEncrypt ke $ \ kePtr -> do
    withRNG rng $ \ botanRNG -> do
        asBytesLen salt $ \ saltPtr saltLen -> do
            alloca $ \ sharedSzPtr -> do
                alloca $ \ encapSzPtr -> do
                    sharedSz <- kemEncryptSharedKeyLength ke desiredLen
                    encapSz <-  kemEncryptEncapsulatedKeyLength ke
                    poke sharedSzPtr (fromIntegral sharedSz)
                    poke encapSzPtr (fromIntegral encapSz)
                    allocBytesWith encapSz $ \ encapPtr -> do
                        allocBytes sharedSz $ \ sharedPtr -> do
                            throwBotanIfNegative_ $ botan_pk_op_kem_encrypt_create_shared_key
                                kePtr
                                botanRNG
                                (ConstPtr saltPtr)
                                saltLen
                                (fromIntegral desiredLen)
                                sharedPtr
                                sharedSzPtr
                                encapPtr
                                encapSzPtr

newtype KEMDecrypt = MkKEMDecrypt { getKEMDecryptForeignPtr :: ForeignPtr BotanPKOpKEMDecryptStruct }

withKEMDecrypt     :: KEMDecrypt -> (BotanPKOpKEMDecrypt -> IO a) -> IO a
kemDecryptDestroy  :: KEMDecrypt -> IO ()
createKEMDecrypt   :: (Ptr BotanPKOpKEMDecrypt -> IO CInt) -> IO KEMDecrypt
(withKEMDecrypt, kemDecryptDestroy, createKEMDecrypt)
    = mkBindings
        MkBotanPKOpKEMDecrypt (.runBotanPKOpKEMDecrypt)
        MkKEMDecrypt (.getKEMDecryptForeignPtr)
        botan_pk_op_kem_decrypt_destroy

kemDecryptCreate
    :: PrivKey          -- ^ __key__
    -> KDFName          -- ^ __kdf__
    -> IO KEMDecrypt    -- ^ __op__
kemDecryptCreate sk algo = withPrivKey sk $ \ skPtr -> do
    asCString algo $ \ algoPtr -> do
        createKEMDecrypt $ \ out -> botan_pk_op_kem_decrypt_create
            out
            skPtr
            (ConstPtr algoPtr)

kemDecryptSharedKeyLength
    :: KEMDecrypt   -- ^ __op__
    -> Int          -- ^ __desired_shared_key_length__
    -> IO Int       -- ^ __output_shared_key_length__
kemDecryptSharedKeyLength = mkGetSize_csize withKEMDecrypt botan_pk_op_kem_decrypt_shared_key_length

kemDecryptSharedKey
    :: KEMDecrypt           -- ^ __op__
    -> ByteString           -- ^ __salt[]__
    -> KEMEncapsulatedKey   -- ^ __encapsulated_key[]__
    -> Int                  -- ^ __desired_shared_key_len__
    -> IO KEMSharedKey      -- ^ __shared_key[]__
kemDecryptSharedKey kd salt encap desiredLen = withKEMDecrypt kd $ \ kdPtr -> do
    asBytesLen salt $ \ saltPtr saltLen -> do
        asBytesLen encap $ \ encapPtr encapLen -> do
            -- TODO: Consolidate with allocBytesUpperBound or whatever I end up calling it
            alloca $ \ sharedSzPtr -> do
                sharedSz <- kemDecryptSharedKeyLength kd desiredLen
                poke sharedSzPtr (fromIntegral sharedSz)
                bytes <- allocBytes sharedSz $ \ outPtr -> do
                    throwBotanIfNegative_ $ botan_pk_op_kem_decrypt_shared_key
                        kdPtr
                        (ConstPtr saltPtr)
                        saltLen
                        (ConstPtr encapPtr)
                        encapLen
                        (fromIntegral desiredLen)
                        outPtr
                        sharedSzPtr
                actualSharedSz <- peek sharedSzPtr
                return $!! ByteString.take (fromIntegral actualSharedSz) bytes