packages feed

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 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