packages feed

crypton-2.1.8: Crypto/KEM.hs

-- |
-- Module      : Crypto.KEM
-- License     : BSD-style
-- Maintainer  : Kazu Yamamoto <kazu@iij.ad.jp>
-- Stability   : experimental
-- Portability : unknown
--
-- Key encapsulation: one side publishes a key, the other draws a secret and
-- returns something only the first side can turn back into it.
--
-- The shape is not ML-KEM's alone, which is why this is a class in a module
-- of its own rather than part of any one algorithm: 'Crypto.PubKey.MLKEM'
-- is one instance of it, and a caller that does not care which it has can
-- be written against this.
--
-- A bare Diffie-Hellman exchange is deliberately __not__ an instance, even
-- though the shapes line up.  The KEM a Diffie-Hellman group gives is
-- DHKEM, of RFC 9180 section 4.1, and that is not the raw exchange: its
-- shared secret is the exchange's output run through HKDF with the
-- ephemeral and the recipient public keys as context, under labels that
-- name the ciphersuite.  The binding to those two keys, and the separation
-- between suites, are what the KEM security argument rests on; the raw
-- value has neither.  A protocol can supply the binding at its own level --
-- TLS 1.3 does, in the key schedule -- but then the binding belongs to the
-- protocol, not to this class, and offering the raw exchange here would
-- invite its use somewhere that supplies nothing.
--
-- DHKEM proper is an instance, and it is not here either: the labels it
-- derives under carry the HPKE ciphersuite identifier, which is an IANA
-- registry value rather than anything a primitive knows, so the instances
-- live beside the registry in the @hpke@ package.
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TypeFamilies #-}

module Crypto.KEM (
    KEM (..),
    SharedSecret (..),
) where

import Data.Kind (Type)

import Crypto.Error (CryptoFailable)
import Crypto.Internal.ByteArray (ByteArrayAccess, ScrubbedBytes)
import Crypto.Internal.Imports
import Crypto.Random (MonadRandom)

-- | Secret shared via key exchange.
newtype SharedSecret = SharedSecret ScrubbedBytes
    deriving (Eq, ByteArrayAccess, NFData)

instance Show SharedSecret where
    show _ = "SharedSecret <redacted>"

instance Semigroup SharedSecret where
    SharedSecret x <> SharedSecret y = SharedSecret (x <> y)

instance Monoid SharedSecret where
    mempty = SharedSecret mempty

-- | A key encapsulation mechanism.
--
-- The three values are named for what they do rather than for what they are
-- in any one algorithm.  In ML-KEM the encapsulation key and the ciphertext
-- are what the names say; in a Diffie-Hellman exchange both are public
-- values of the group, and the \"ciphertext\" is the ephemeral one the
-- encapsulating side generates.  Keeping them apart in the types is what
-- stops one being passed where the other belongs, which they are not
-- interchangeable for even where they have the same representation.
class KEM kem where
    -- | What the encapsulating side is given.
    type EncapsulationKey kem :: Type

    -- | What the decapsulating side keeps.
    type DecapsulationKey kem :: Type

    -- | What travels back, and is decapsulated.
    type Ciphertext kem :: Type

    -- | The randomness 'encapsulate' draws, for the instances that let a
    -- caller supply it.  In ML-KEM it is @m@ of FIPS 203, a string of
    -- bytes; in DHKEM it is the ephemeral secret key, a scalar of the
    -- group.  Both are secret, and both determine the shared secret
    -- completely.
    type Coins kem :: Type

    -- | Generate a key pair for the decapsulating side.
    generateKeyPair
        :: MonadRandom m
        => proxy kem -> m (EncapsulationKey kem, DecapsulationKey kem)

    -- | Draw a secret and encapsulate it against the key.
    --
    -- This can fail, and does for some instances: a Diffie-Hellman exchange
    -- refuses a peer value that would make the secret degenerate, where
    -- ML-KEM has nothing to refuse.
    encapsulate
        :: MonadRandom m
        => proxy kem
        -> EncapsulationKey kem
        -> m (CryptoFailable (Ciphertext kem, SharedSecret))

    -- | Encapsulate with the randomness supplied rather than drawn.
    --
    -- The secret this produces is a deterministic function of the key and
    -- these coins, so they must come from a source no other party can
    -- predict or repeat, and must not be used twice.  'encapsulate' is the
    -- entry point for ordinary use; this one is for test vectors, and for a
    -- protocol that has to name the ephemeral value it used -- HPKE lets a
    -- sender supply its own, and the RFC 9180 vectors are written that way.
    encapsulateWith
        :: proxy kem
        -> EncapsulationKey kem
        -> Coins kem
        -> CryptoFailable (Ciphertext kem, SharedSecret)

    -- | Recover the secret.
    decapsulate
        :: proxy kem
        -> DecapsulationKey kem
        -> Ciphertext kem
        -> CryptoFailable SharedSecret