hpke-0.3.0: Crypto/HPKE/KEM.hs
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
module Crypto.HPKE.KEM (
HPKEKEM (..),
KEMAlg (..),
encapEnv,
decapEnv,
)
where
import Crypto.ECC (
EllipticCurve (..),
EllipticCurveDH (..),
)
import Crypto.KEM (KEM (..))
import Crypto.Random (MonadRandom)
import Crypto.HPKE.PublicKey
import Crypto.HPKE.Types
----------------------------------------------------------------
-- | What HPKE needs of a KEM beyond what @crypton@'s 'KEM' class says.
--
-- RFC 9180 section 4.1 gives DHKEM four operations and the class has room
-- for two: @Encap@ is 'encapsulate', @Decap@ is 'decapsulate'. @AuthEncap@
-- and @AuthDecap@ take a static sender key, which the class has no argument
-- for, so they are here. They default to refusing, which is what a KEM
-- with no authenticated mode -- any post-quantum one -- should do.
--
-- The equalities pin the four associated types to the serialized forms of
-- section 4, which is what travels and what this package's entry points
-- take, so that 'Crypto.HPKE.setupS' can be written once for every KEM.
class
( KEM kem
, EncapsulationKey kem ~ EncodedPublicKey
, DecapsulationKey kem ~ EncodedSecretKey
, Ciphertext kem ~ EncodedPublicKey
, Coins kem ~ EncodedSecretKey
) =>
HPKEKEM kem
where
-- | Draw what 'encapsulate' would have drawn, so that the
-- authenticated forms can generate their ephemeral key the same way.
--
-- For every KEM here the coins are a secret key, so the default is to
-- draw a key pair and keep the half that is one.
generateCoins :: MonadRandom m => proxy kem -> m EncodedSecretKey
generateCoins p = snd `fmap` generateKeyPair p
-- | @AuthEncap@ of section 4.1, with the ephemeral key supplied.
authEncapWith
:: proxy kem
-> EncodedSecretKey
-- ^ @skS@, the sender's static key
-> EncodedPublicKey
-- ^ @pkR@
-> EncodedSecretKey
-- ^ @skE@
-> Either HPKEError (EncodedPublicKey, SharedSecret)
authEncapWith _ _ _ _ = Left $ Unsupported "authenticated mode"
-- | @AuthDecap@ of section 4.1. The sender is named by its public
-- key: a receiver does not have the other side's secret key, and
-- asking for one would be asking for the wrong thing.
authDecap
:: proxy kem
-> EncodedPublicKey
-- ^ @pkS@, the sender's static public key
-> EncodedSecretKey
-- ^ @skR@
-> EncodedPublicKey
-- ^ @enc@
-> Either HPKEError SharedSecret
authDecap _ _ _ _ = Left $ Unsupported "authenticated mode"
-- | The encapsulation key a decapsulation key belongs to.
toEncapsulationKey
:: proxy kem -> EncodedSecretKey -> Either HPKEError EncodedPublicKey
-- | A KEM that HPKE can run over, with its identity forgotten, for the
-- table that turns a @KEM_ID@ into one.
data KEMAlg = forall kem. HPKEKEM kem => KEMAlg (Proxy kem)
----------------------------------------------------------------
-- | @Encap@ and @AuthEncap@ of RFC 9180 section 4.1, with the ephemeral key
-- supplied.
--
-- Written out the same way 'decap' is: what it needs are the ephemeral key,
-- the sender's static key if the mode is authenticated, and the recipient's
-- public key.
encap
:: (EllipticCurve group, EllipticCurveDH group)
=> Proxy group
-> KeyDeriveFunction
-> SecretKey group
-> Maybe (SecretKey group)
-> Encap
encap proxy derive skE mskS enc0@(EncodedPublicKey pkRm) = do
pkR <- deserializePublicKey proxy enc0
dh0 <- ecdh' proxy skE pkR $ EncapError "encap"
(dh, pkSm) <- case mskS of
Nothing -> return (dh0, "")
Just skS -> do
let pkS = scalarToPoint proxy skS
dh1 <- ecdh' proxy skS pkR $ EncapError "encap"
let EncodedPublicKey pk = serializePublicKey proxy pkS
return (dh0 <> dh1, pk)
let pkE = scalarToPoint proxy skE
let enc@(EncodedPublicKey pkEm) = serializePublicKey proxy pkE
kem_context = pkEm <> pkRm <> pkSm
shared_secret = SharedSecret $ convert $ derive dh kem_context
return (shared_secret, enc)
encapEnv
:: (EllipticCurve group, EllipticCurveDH group)
=> Proxy group
-> KeyDeriveFunction
-> EncodedSecretKey
-> Maybe EncodedSecretKey
-> Encap
encapEnv proxy derive skEm mskSm enc = do
skE <- deserializeSecretKey proxy skEm
mskS <- traverse (deserializeSecretKey proxy) mskSm
encap proxy derive skE mskS enc
----------------------------------------------------------------
-- | @Decap@ and @AuthDecap@ of RFC 9180 section 4.1.
--
-- The sender is named by its public key, because that is all a receiver
-- has: @AuthDecap(enc, skR, pkS)@.
decap
:: (EllipticCurve group, EllipticCurveDH group)
=> Proxy group
-> KeyDeriveFunction
-> SecretKey group
-> Maybe (PublicKey group)
-> Decap
decap proxy derive skR mpkS enc@(EncodedPublicKey pkEm) = do
pkE <- deserializePublicKey proxy enc
dh0 <- ecdh' proxy skR pkE $ DecapError "decap"
(dh, pkSm) <- case mpkS of
Nothing -> return (dh0, "")
Just pkS -> do
dh1 <- ecdh' proxy skR pkS $ DecapError "decap"
let EncodedPublicKey pk = serializePublicKey proxy pkS
return (dh0 <> dh1, pk)
let pkR = scalarToPoint proxy skR
let EncodedPublicKey pkRm = serializePublicKey proxy pkR
kem_context = pkEm <> pkRm <> pkSm
shared_secret = SharedSecret $ convert $ derive dh kem_context
return shared_secret
decapEnv
:: (EllipticCurve group, EllipticCurveDH group)
=> Proxy group
-> KeyDeriveFunction
-> EncodedSecretKey
-> Maybe EncodedPublicKey
-> Decap
decapEnv proxy derive skRm mpkSm enc = do
skR <- deserializeSecretKey proxy skRm
mpkS <- traverse (deserializePublicKey proxy) mpkSm
decap proxy derive skR mpkS enc
----------------------------------------------------------------
ecdh'
:: EllipticCurveDH group
=> Proxy group
-> SecretKey group
-> PublicKey group
-> a
-> Either a SharedSecret
ecdh' proxy sk pk err = case ecdh proxy sk pk of
CryptoPassed a -> Right a
CryptoFailed _ -> Left err