diff --git a/src/Simplex/Messaging/Crypto/SNTRUP761/Bindings.hs b/src/Simplex/Messaging/Crypto/SNTRUP761/Bindings.hs index 73d4c6885..861abf69e 100644 --- a/src/Simplex/Messaging/Crypto/SNTRUP761/Bindings.hs +++ b/src/Simplex/Messaging/Crypto/SNTRUP761/Bindings.hs @@ -1,11 +1,14 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE TypeApplications #-} +{-# LANGUAGE PatternSynonyms #-} module Simplex.Messaging.Crypto.SNTRUP761.Bindings - ( KEMPublicKey (..), + ( KEMPublicKey, KEMSecretKey, - KEMCiphertext (..), - KEMSharedKey (..), + KEMCiphertext, + KEMSharedKey, + pattern KEMPublicKey, + pattern KEMSharedKey, KEMKeyPair, sntrup761Keypair, sntrup761Enc, @@ -25,8 +28,9 @@ import Simplex.Messaging.Crypto.SNTRUP761.Bindings.FFI import Simplex.Messaging.Crypto.SNTRUP761.Bindings.RNG (rngFuncPtr, withDRG) import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding.String +import Simplex.Messaging.Util ((<$?>)) -newtype KEMPublicKey = KEMPublicKey ByteString +newtype KEMPublicKey = KEMPublicKey_ ByteString deriving (Eq, Show) newtype KEMSecretKey = KEMSecretKey ScrubbedBytes @@ -35,18 +39,24 @@ newtype KEMSecretKey = KEMSecretKey ScrubbedBytes newtype KEMCiphertext = KEMCiphertext ByteString deriving (Eq, Show) -newtype KEMSharedKey = KEMSharedKey ScrubbedBytes +newtype KEMSharedKey = KEMSharedKey_ ScrubbedBytes deriving (Eq, Show) -unsafeRevealKEMSharedKey :: KEMSharedKey -> String -unsafeRevealKEMSharedKey (KEMSharedKey scrubbed) = show (BA.convert scrubbed :: ByteString) -{-# DEPRECATED unsafeRevealKEMSharedKey "unsafeRevealKEMSharedKey left in code" #-} +pattern KEMPublicKey :: ByteString -> KEMPublicKey +pattern KEMPublicKey s <- KEMPublicKey_ s + +pattern KEMSharedKey :: ScrubbedBytes -> KEMSharedKey +pattern KEMSharedKey s <- KEMSharedKey_ s + +{-# COMPLETE KEMPublicKey #-} + +{-# COMPLETE KEMSharedKey #-} type KEMKeyPair = (KEMPublicKey, KEMSecretKey) sntrup761Keypair :: TVar ChaChaDRG -> IO KEMKeyPair sntrup761Keypair drg = - bimap KEMPublicKey KEMSecretKey + bimap KEMPublicKey_ KEMSecretKey <$> BA.allocRet c_SNTRUP761_SECRETKEY_SIZE ( \skPtr -> @@ -57,7 +67,7 @@ sntrup761Keypair drg = sntrup761Enc :: TVar ChaChaDRG -> KEMPublicKey -> IO (KEMCiphertext, KEMSharedKey) sntrup761Enc drg (KEMPublicKey pk) = BA.withByteArray pk $ \pkPtr -> - bimap KEMCiphertext KEMSharedKey + bimap KEMCiphertext KEMSharedKey_ <$> BA.allocRet c_SNTRUP761_SIZE ( \kPtr -> @@ -69,40 +79,47 @@ sntrup761Dec :: KEMCiphertext -> KEMSecretKey -> IO KEMSharedKey sntrup761Dec (KEMCiphertext c) (KEMSecretKey sk) = BA.withByteArray sk $ \skPtr -> BA.withByteArray c $ \cPtr -> - KEMSharedKey + KEMSharedKey_ <$> BA.alloc c_SNTRUP761_SIZE (\kPtr -> c_sntrup761_dec kPtr cPtr skPtr) +parseKey :: BA.ByteArrayAccess bs => (bs -> key) -> String -> Int -> bs -> Either String key +parseKey kCon name expected s + | len == expected = Right $ kCon s + | otherwise = Left $ name <> " must be " <> show expected <> " bytes, got " <> show len + where + len = BA.length s + instance Encoding KEMSecretKey where smpEncode (KEMSecretKey c) = smpEncode . Large $ BA.convert c - smpP = KEMSecretKey . BA.convert . unLarge <$> smpP + smpP = parseKey KEMSecretKey "SNTRUP761 secret key" c_SNTRUP761_SECRETKEY_SIZE . BA.convert . unLarge <$?> smpP instance StrEncoding KEMSecretKey where strEncode (KEMSecretKey pk) = strEncode (BA.convert pk :: ByteString) - strP = KEMSecretKey . BA.convert <$> strP @ByteString + strP = parseKey KEMSecretKey "SNTRUP761 secret key" c_SNTRUP761_SECRETKEY_SIZE . BA.convert <$?> strP @ByteString instance Encoding KEMPublicKey where smpEncode (KEMPublicKey pk) = smpEncode . Large $ BA.convert pk - smpP = KEMPublicKey . BA.convert . unLarge <$> smpP + smpP = parseKey KEMPublicKey_ "SNTRUP761 public key" c_SNTRUP761_PUBLICKEY_SIZE . unLarge <$?> smpP instance StrEncoding KEMPublicKey where strEncode (KEMPublicKey pk) = strEncode (BA.convert pk :: ByteString) - strP = KEMPublicKey . BA.convert <$> strP @ByteString + strP = parseKey KEMPublicKey_ "SNTRUP761 public key" c_SNTRUP761_PUBLICKEY_SIZE <$?> strP @ByteString instance Encoding KEMCiphertext where smpEncode (KEMCiphertext c) = smpEncode . Large $ BA.convert c - smpP = KEMCiphertext . BA.convert . unLarge <$> smpP + smpP = parseKey KEMCiphertext "SNTRUP761 ciphertext" c_SNTRUP761_CIPHERTEXT_SIZE . unLarge <$?> smpP instance Encoding KEMSharedKey where smpEncode (KEMSharedKey c) = smpEncode (BA.convert c :: ByteString) - smpP = KEMSharedKey . BA.convert <$> smpP @ByteString + smpP = KEMSharedKey_ . BA.convert <$> smpP @ByteString instance StrEncoding KEMCiphertext where strEncode (KEMCiphertext pk) = strEncode (BA.convert pk :: ByteString) - strP = KEMCiphertext . BA.convert <$> strP @ByteString + strP = parseKey KEMCiphertext "SNTRUP761 ciphertext" c_SNTRUP761_CIPHERTEXT_SIZE <$?> strP @ByteString instance StrEncoding KEMSharedKey where strEncode (KEMSharedKey pk) = strEncode (BA.convert pk :: ByteString) - strP = KEMSharedKey . BA.convert <$> strP @ByteString + strP = KEMSharedKey_ . BA.convert <$> strP @ByteString instance ToJSON KEMSecretKey where toJSON = strToJSON @@ -130,9 +147,9 @@ instance ToField KEMSharedKey where instance FromField KEMSharedKey where #if defined(dbPostgres) - fromField f dat = KEMSharedKey . BA.convert @ByteString <$> fromField f dat + fromField f dat = KEMSharedKey_ . BA.convert @ByteString <$> fromField f dat #else - fromField f = KEMSharedKey . BA.convert @ByteString <$> fromField f + fromField f = KEMSharedKey_ . BA.convert @ByteString <$> fromField f #endif instance ToJSON KEMSharedKey where diff --git a/tests/CoreTests/CryptoTests.hs b/tests/CoreTests/CryptoTests.hs index abf5373da..ee3bc03f5 100644 --- a/tests/CoreTests/CryptoTests.hs +++ b/tests/CoreTests/CryptoTests.hs @@ -1,6 +1,7 @@ {-# LANGUAGE GADTs #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} {-# OPTIONS_GHC -Wno-orphans #-} module CoreTests.CryptoTests (cryptoTests) where @@ -10,7 +11,7 @@ import Control.Monad.Except import qualified Data.Aeson as J import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy.Char8 as LB -import Data.Either (isRight) +import Data.Either (isLeft, isRight) import Data.Int (Int64) import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) @@ -25,10 +26,13 @@ import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto.Lazy as LC import Simplex.Messaging.Crypto.BBS import Simplex.Messaging.Crypto.SNTRUP761.Bindings +import Simplex.Messaging.Crypto.SNTRUP761.Bindings.Defines +import Simplex.Messaging.Encoding (Large (..), smpDecode, smpEncode) +import Simplex.Messaging.Encoding.String (strDecode, strEncode) import Simplex.Messaging.Transport.Client import Test.Hspec hiding (fit, it) import Test.Hspec.QuickCheck (modifyMaxSuccess) -import Test.QuickCheck +import Test.QuickCheck hiding (Large) import Util cryptoTests :: Spec @@ -101,8 +105,9 @@ cryptoTests = do describe "X448" $ testEncoding C.SX448 describe "X509 chains" $ do it "should validate certificates" testValidateX509 - describe "sntrup761" $ + describe "sntrup761" $ do it "should enc/dec key" testSNTRUP761 + it "should reject malformed KEM encodings" testSNTRUP761RejectsMalformedEncodings describe "BBS+" $ do it "should sign and verify" testBBSSignVerify it "should derive public key from secret key" testBBSPublicKeyDerivation @@ -284,6 +289,34 @@ testSNTRUP761 = do KEMSharedKey k' <- sntrup761Dec c sk k' `shouldBe` k +testSNTRUP761RejectsMalformedEncodings :: IO () +testSNTRUP761RejectsMalformedEncodings = do + smpDecode @KEMPublicKey (smpEncode $ Large shortPublicKey) `shouldSatisfy` isLeft + strDecode @KEMPublicKey (strEncode shortPublicKey) `shouldSatisfy` isLeft + smpDecode @KEMPublicKey (smpEncode $ Large validPublicKey) `shouldSatisfy` isRight + strDecode @KEMPublicKey (strEncode validPublicKey) `shouldSatisfy` isRight + smpDecode @KEMCiphertext (smpEncode $ Large shortCiphertext) `shouldSatisfy` isLeft + strDecode @KEMCiphertext (strEncode shortCiphertext) `shouldSatisfy` isLeft + smpDecode @KEMCiphertext (smpEncode $ Large validCiphertext) `shouldSatisfy` isRight + strDecode @KEMCiphertext (strEncode validCiphertext) `shouldSatisfy` isRight + smpDecode @KEMSecretKey (smpEncode $ Large shortSecretKey) `shouldSatisfy` isLeft + strDecode @KEMSecretKey (strEncode shortSecretKey) `shouldSatisfy` isLeft + +shortPublicKey :: B.ByteString +shortPublicKey = B.replicate (c_SNTRUP761_PUBLICKEY_SIZE - 1) 'p' + +validPublicKey :: B.ByteString +validPublicKey = B.replicate c_SNTRUP761_PUBLICKEY_SIZE 'p' + +shortCiphertext :: B.ByteString +shortCiphertext = B.replicate (c_SNTRUP761_CIPHERTEXT_SIZE - 1) 'c' + +validCiphertext :: B.ByteString +validCiphertext = B.replicate c_SNTRUP761_CIPHERTEXT_SIZE 'c' + +shortSecretKey :: B.ByteString +shortSecretKey = B.replicate (c_SNTRUP761_SECRETKEY_SIZE - 1) 's' + -- BBS+ tests bbsHeader :: BBSHeader