hpke-0.3.0: test/DHKEMSpec.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
module DHKEMSpec where
import Crypto.Error (CryptoFailable (..))
import Data.ByteArray (convert)
import Data.ByteString (ByteString)
import qualified Data.ByteString.Base16 as B16
import Data.Proxy (Proxy (..))
import Test.Hspec
import Crypto.HPKE
import Crypto.HPKE.DHKEM
x25519 :: Proxy DHKEM_X25519
x25519 = Proxy
p256 :: Proxy DHKEM_P256
p256 = Proxy
hex :: ByteString -> ByteString
hex = B16.decodeLenient
-- RFC 9180 A.1.1, DHKEM(X25519, HKDF-SHA256). The four keys are the ones
-- A1Spec already drives the whole of HPKE with; the shared secret is the
-- value the appendix gives for the KEM step alone, which nothing else here
-- reaches.
skEm, pkEm, skRm, pkRm, sharedSecret :: ByteString
skEm = "52c4a758a802cd8b936eceea314432798d5baf2d7e9235dc084ab1b9cfa2f736"
pkEm = "37fda3567bdbd628e88668c3c8d7e97d1d1253b6d4ea6d44c150f741f1bf4431"
skRm = "4612c550263fc8ad58375df3f557aac531d26850903e55a9f23f21d8534e8ac8"
pkRm = "3948cfe0ad1ddb695d780e59077195da6c56506b027329794ab02bca80815c4d"
sharedSecret = "fe0e18c9f024ce43799ae393c7e8fe8fce9d218875e8227b0187c04e7d2ea1fc"
spec :: Spec
spec = do
describe "DHKEM through crypton's KEM class" $ do
it "A.1.1: encapsulating with the vector's ephemeral key" $
case encapsulateWith
x25519
(EncodedPublicKey (hex pkRm))
(EncodedSecretKey (hex skEm)) of
CryptoFailed e -> expectationFailure (show e)
CryptoPassed (EncodedPublicKey enc, ss) -> do
enc `shouldBe` hex pkEm
(convert ss :: ByteString) `shouldBe` hex sharedSecret
it "A.1.1: the receiver recovers that secret" $
case decapsulate
x25519
(EncodedSecretKey (hex skRm))
(EncodedPublicKey (hex pkEm)) of
CryptoFailed e -> expectationFailure (show e)
CryptoPassed ss ->
(convert ss :: ByteString) `shouldBe` hex sharedSecret
it "a generated X25519 pair encapsulates and decapsulates" $
roundTrip x25519
it "a generated P-256 pair encapsulates and decapsulates" $
roundTrip p256
it "the authenticated mode binds the sender's public key" $ do
(pkS, skS) <- generateKeyPair x25519
(pkR, skR) <- generateKeyPair x25519
(pkOther, _) <- generateKeyPair x25519
skE <- generateCoins x25519
case authEncapWith x25519 skS pkR skE of
Left e -> expectationFailure (show e)
Right (enc, ss) -> do
-- the receiver has the sender's public key, not its
-- secret one, which is the whole point of the argument
authDecap x25519 pkS skR enc `shouldBe` Right ss
authDecap x25519 pkOther skR enc `shouldNotBe` Right ss
it "refuses an encapsulated value that is not a point" $ do
(_, sk) <- generateKeyPair x25519
case decapsulate x25519 sk (EncodedPublicKey "short") of
CryptoPassed _ -> expectationFailure "a 5-byte point was accepted"
CryptoFailed _ -> return ()
roundTrip
:: ( KEM kem
, EncapsulationKey kem ~ EncodedPublicKey
, DecapsulationKey kem ~ EncodedSecretKey
, Ciphertext kem ~ EncodedPublicKey
)
=> Proxy kem -> Expectation
roundTrip p = do
(pk, sk) <- generateKeyPair p
r <- encapsulate p pk
case r of
CryptoFailed e -> expectationFailure (show e)
CryptoPassed (enc, ss) -> case decapsulate p sk enc of
CryptoFailed e -> expectationFailure (show e)
CryptoPassed ss' ->
(convert ss' :: ByteString) `shouldBe` convert ss