cryptonite-conduit 0.2.0 → 0.2.1
raw patch · 5 files changed
+223/−3 lines, 5 filesdep +exceptionsdep +tasty-quickcheckdep ~basePVP ok
version bump matches the API change (PVP)
Dependencies added: exceptions, tasty-quickcheck
Dependency ranges changed: base
API changes (from Hackage documentation)
+ Crypto.Cipher.ChaChaPoly1305.Conduit: DecryptKeyException :: !CryptoError -> ChaChaException
+ Crypto.Cipher.ChaChaPoly1305.Conduit: DecryptNonceException :: !CryptoError -> ChaChaException
+ Crypto.Cipher.ChaChaPoly1305.Conduit: EncryptKeyException :: !CryptoError -> ChaChaException
+ Crypto.Cipher.ChaChaPoly1305.Conduit: EncryptNonceException :: !CryptoError -> ChaChaException
+ Crypto.Cipher.ChaChaPoly1305.Conduit: MismatchedAuth :: ChaChaException
+ Crypto.Cipher.ChaChaPoly1305.Conduit: data ChaChaException
+ Crypto.Cipher.ChaChaPoly1305.Conduit: decrypt :: MonadThrow m => ByteString -> ConduitM ByteString ByteString m ()
+ Crypto.Cipher.ChaChaPoly1305.Conduit: encrypt :: MonadThrow m => ByteString -> ByteString -> ConduitM ByteString ByteString m ()
+ Crypto.Cipher.ChaChaPoly1305.Conduit: instance GHC.Exception.Exception Crypto.Cipher.ChaChaPoly1305.Conduit.ChaChaException
+ Crypto.Cipher.ChaChaPoly1305.Conduit: instance GHC.Show.Show Crypto.Cipher.ChaChaPoly1305.Conduit.ChaChaException
+ Crypto.PubKey.ECIES.Conduit: decrypt :: (MonadThrow m) => Scalar Curve -> ConduitM ByteString ByteString m ()
+ Crypto.PubKey.ECIES.Conduit: encrypt :: (MonadThrow m, MonadRandom m) => Point Curve -> ConduitM ByteString ByteString m ()
Files
- CHANGELOG.md +12/−0
- Crypto/Cipher/ChaChaPoly1305/Conduit.hs +88/−0
- Crypto/PubKey/ECIES/Conduit.hs +80/−0
- cryptonite-conduit.cabal +7/−3
- test/Spec.hs +36/−0
+ CHANGELOG.md view
@@ -0,0 +1,12 @@+## 0.2.1++* Add the `Crypto.Cipher.ChaChaPoly1305.Conduit` and+ `Crypto.PubKey.ECIES.Conduit` modules++## 0.2.0++* Add HMAC sink++## 0.1++* Initial release
+ Crypto/Cipher/ChaChaPoly1305/Conduit.hs view
@@ -0,0 +1,88 @@+{-# LANGUAGE DeriveDataTypeable #-}+module Crypto.Cipher.ChaChaPoly1305.Conduit+ ( encrypt+ , decrypt+ , ChaChaException (..)+ ) where++import Control.Exception (assert)+import Control.Monad.Catch (Exception, MonadThrow, throwM)+import qualified Crypto.Cipher.ChaChaPoly1305 as Cha+import qualified Crypto.Error as CE+import qualified Crypto.MAC.Poly1305 as Poly1305+import qualified Data.ByteArray as BA+import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as BL+import Data.Conduit (ConduitM, await, leftover, yield)+import qualified Data.Conduit.Binary as CB+import Data.Typeable (Typeable)++cf :: MonadThrow m+ => (CE.CryptoError -> ChaChaException)+ -> CE.CryptoFailable a+ -> m a+cf _ (CE.CryptoPassed x) = return x+cf f (CE.CryptoFailed e) = throwM (f e)++data ChaChaException+ = EncryptNonceException !CE.CryptoError+ | EncryptKeyException !CE.CryptoError+ | DecryptNonceException !CE.CryptoError+ | DecryptKeyException !CE.CryptoError+ | MismatchedAuth+ deriving (Show, Typeable)+instance Exception ChaChaException++encrypt+ :: MonadThrow m+ => ByteString -- ^ nonce (12 random bytes)+ -> ByteString -- ^ symmetric key (32 bytes)+ -> ConduitM ByteString ByteString m ()+encrypt nonceBS key = do+ nonce <- cf EncryptNonceException $ Cha.nonce12 nonceBS+ state0 <- cf EncryptKeyException $ Cha.initialize key nonce+ yield nonceBS+ let loop state1 = do+ mbs <- await+ case mbs of+ Nothing -> yield $ BA.convert $ Cha.finalize state1+ Just bs -> do+ let (bs', state2) = Cha.encrypt bs state1+ yield bs'+ loop state2+ loop $ Cha.finalizeAAD state0++decrypt+ :: MonadThrow m+ => ByteString -- ^ symmetric key (32 bytes)+ -> ConduitM ByteString ByteString m ()+decrypt key = do+ nonceBS <- CB.take 12+ nonce <- cf DecryptNonceException $ Cha.nonce12 $ BL.toStrict nonceBS+ state0 <- cf DecryptKeyException $ Cha.initialize key nonce+ let loop state1 = do+ ebs <- awaitExcept16 id+ case ebs of+ Left final ->+ case Poly1305.authTag final of+ CE.CryptoPassed final' | Cha.finalize state1 == final' -> return ()+ _ -> throwM MismatchedAuth+ Right bs -> do+ let (bs', state2) = Cha.decrypt bs state1+ yield bs'+ loop state2+ loop $ Cha.finalizeAAD state0+ where+ awaitExcept16 front = do+ mbs <- await+ case mbs of+ Nothing -> return $ Left $ front B.empty+ Just bs -> do+ let bs' = front bs+ if B.length bs' > 16+ then do+ let (x, y) = B.splitAt (B.length bs' - 16) bs'+ assert (B.length y == 16) leftover y+ return $ Right x+ else awaitExcept16 (B.append bs')
+ Crypto/PubKey/ECIES/Conduit.hs view
@@ -0,0 +1,80 @@+{-# LANGUAGE CPP #-}+module Crypto.PubKey.ECIES.Conduit+ ( encrypt+ , decrypt+ ) where++import Control.Monad.Catch (MonadThrow, throwM)+import Control.Monad.Trans.Class (lift)+import qualified Crypto.Cipher.ChaCha as ChaCha+import qualified Crypto.Cipher.ChaChaPoly1305.Conduit as ChaCha+import qualified Crypto.ECC as ECC+import qualified Crypto.Error as CE+import Crypto.Hash (SHA512 (..), hashWith)+import Crypto.PubKey.ECIES (deriveDecrypt,+ deriveEncrypt)+import Crypto.Random (MonadRandom)+import qualified Data.ByteArray as BA+import Data.ByteString (ByteString)+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as BL+import Data.Conduit (ConduitM, yield)+import qualified Data.Conduit.Binary as CB+import Data.Proxy (Proxy (..))+import System.IO.Unsafe (unsafePerformIO)++getNonceKey :: ECC.SharedSecret -> (ByteString, ByteString)+getNonceKey shared =+ let state1 = ChaCha.initializeSimple $ B.take 40 $ BA.convert $ hashWith SHA512 shared+ (nonce, state2) = ChaCha.generateSimple state1 12+ (key, _) = ChaCha.generateSimple state2 32+ in (nonce, key)++type Curve = ECC.Curve_P256R1++proxy :: Proxy Curve+proxy = Proxy++pointBinarySize :: Int+pointBinarySize = B.length $ ECC.encodePoint proxy point+ where+ point = unsafePerformIO (ECC.keypairGetPublic <$> ECC.curveGenerateKeyPair proxy)+{-# NOINLINE pointBinarySize #-}++throwOnFail :: MonadThrow m => CE.CryptoFailable a -> m a+throwOnFail (CE.CryptoPassed a) = pure a+throwOnFail (CE.CryptoFailed e) = throwM e+++encrypt+ :: (MonadThrow m, MonadRandom m)+ => ECC.Point Curve+ -> ConduitM ByteString ByteString m ()+encrypt point = do+ (point', shared) <- lift (deriveEncryptCompat proxy point) >>= throwOnFail+ let (nonce, key) = getNonceKey shared+ yield $ ECC.encodePoint proxy point'+ ChaCha.encrypt nonce key+ where+#if MIN_VERSION_cryptonite(0,23,999)+ deriveEncryptCompat prx p = deriveEncrypt prx p+#else+ deriveEncryptCompat prx p = CE.CryptoPassed <$> deriveEncrypt prx p+#endif++decrypt+ :: (MonadThrow m)+ => ECC.Scalar Curve+ -> ConduitM ByteString ByteString m ()+decrypt scalar = do+ pointBS <- fmap BL.toStrict $ CB.take pointBinarySize+ point <- throwOnFail (ECC.decodePoint proxy pointBS)+ shared <- throwOnFail (deriveDecryptCompat proxy point scalar)+ let (_nonce, key) = getNonceKey shared+ ChaCha.decrypt key+ where+#if MIN_VERSION_cryptonite(0,23,999)+ deriveDecryptCompat prx p s = deriveDecrypt prx p s+#else+ deriveDecryptCompat prx p s = CE.CryptoPassed (deriveDecrypt prx p s)+#endif
cryptonite-conduit.cabal view
@@ -1,5 +1,5 @@ Name: cryptonite-conduit-Version: 0.2.0+version: 0.2.1 Synopsis: cryptonite conduit Description: Conduit bridge for cryptonite@@ -17,16 +17,19 @@ Build-Type: Simple Homepage: https://github.com/haskell-crypto/cryptonite-conduit Cabal-Version: >=1.8-Extra-source-files: README.md+Extra-source-files: README.md CHANGELOG.md Library- Exposed-modules: Crypto.MAC.HMAC.Conduit+ Exposed-modules: Crypto.Cipher.ChaChaPoly1305.Conduit+ Crypto.MAC.HMAC.Conduit Crypto.Hash.Conduit+ Crypto.PubKey.ECIES.Conduit Build-depends: base >= 4 && < 5 , bytestring , conduit , conduit-extra , cryptonite+ , exceptions , memory , resourcet , transformers@@ -45,6 +48,7 @@ , memory , tasty , tasty-hunit+ , tasty-quickcheck ghc-options: -threaded -rtsopts -with-rtsopts=-N -W -Wall source-repository head
test/Spec.hs view
@@ -1,11 +1,18 @@ {-# Language OverloadedStrings #-} import Conduit+import qualified Crypto.Cipher.ChaChaPoly1305.Conduit as ChaCha+import qualified Crypto.ECC as ECC import Crypto.Hash import Crypto.MAC.HMAC import Crypto.MAC.HMAC.Conduit+import qualified Crypto.PubKey.ECIES.Conduit as PubKey+import Crypto.Random import Data.ByteArray.Encoding+import Data.Proxy (Proxy (..))+import Data.Word (Word8) import Test.Tasty import Test.Tasty.HUnit+import Test.Tasty.QuickCheck import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as BL @@ -17,6 +24,12 @@ [ testGroup "HMAC" [ testCase "File HMAC is correct" testFileHMAC ]+ , testGroup "ChaChaPoly1305"+ [ testProperty "encrypt/decrypt works" (ioProperty . propChaChaPoly1305)+ ]+ , testGroup "publicECC"+ [ testProperty "encrypt/decrypt works" (ioProperty . propPublicECC)+ ] ] testFileHMAC :: Assertion@@ -25,3 +38,26 @@ testhmac <- runConduit $ sourceLazy source $$ sinkHMAC ("foobar" :: BS.ByteString) let hexdump = convertToBase Base16 (testhmac :: HMAC SHA512t_256) assertEqual "HMAC mismatch" "ab78ef7a3a7b02b2ef50ee1a17e43ae0c134e0bece468b047780626264301831" (hexdump :: BS.ByteString)++propChaChaPoly1305 :: [[Word8]] -> IO Bool+propChaChaPoly1305 octets = do+ let chunksIn = map BS.pack octets+ nonce <- getRandomBytes 12+ key <- getRandomBytes 32+ chunksOut <- runConduit+ $ mapM_ yield chunksIn+ .| ChaCha.encrypt nonce key+ .| ChaCha.decrypt key+ .| sinkLazy+ return $ BL.fromChunks chunksIn == chunksOut++propPublicECC :: [[Word8]] -> IO Bool+propPublicECC octets = do+ let chunksIn = map BS.pack octets+ ECC.KeyPair point scalar <- ECC.curveGenerateKeyPair (Proxy :: Proxy ECC.Curve_P256R1)+ chunksOut <- runConduit+ $ mapM_ yield chunksIn+ .| PubKey.encrypt point+ .| PubKey.decrypt scalar+ .| sinkLazy+ return $ BL.fromChunks chunksIn == chunksOut