packages feed

cryptoids (empty) → 0.0.0

raw patch · 5 files changed

+256/−0 lines, 5 filesdep +basedep +binarydep +bytestringsetup-changed

Dependencies added: base, binary, bytestring, cryptoids-types, cryptonite, memory, mtl

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2017, Gregor Kleen++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Gregor Kleen nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ changes.md view
@@ -0,0 +1,3 @@+# 0.0.0++First published version
+ cryptoids.cabal view
@@ -0,0 +1,34 @@+name: cryptoids+version: 0.0.0+cabal-version: >=1.10+build-type: Simple+license: BSD3+license-file: LICENSE+maintainer: aethoago@141.li+synopsis: Reversable and secure encoding of object ids as a bytestring+category: cryptography+author: Gregor Kleen+extra-source-files:+    changes.md++source-repository head+    type: git+    location: https://git.rheperire.org/cryptoids+    subdir: cryptoids++library+    exposed-modules:+        Data.CryptoID.Poly+    build-depends:+        base >=4.9.1.0 && <4.11,+        cryptoids-types ==0.0.0,+        cryptonite >=0.23 && <0.25,+        bytestring >=0.10.8.1 && <0.11,+        binary >=0.8.3.0 && <0.9,+        memory >=0.14.6 && <0.15,+        mtl >=2.2.1 && <2.3+    default-language: Haskell2010+    default-extensions: RankNTypes DataKinds GeneralizedNewtypeDeriving+                        ViewPatterns RecordWildCards FlexibleContexts+    hs-source-dirs: src+
+ src/Data/CryptoID/Poly.hs view
@@ -0,0 +1,187 @@+{-# LANGUAGE ScopedTypeVariables #-}++{-|+Description: Encryption of bytestrings using a type level nonce for determinism+License: BSD3++Given a value of a serializable type (like 'Int') we perform serialization and+compute a cryptographic hash of the associated namespace (carried as a phantom+type of kind 'Symbol').+The serialized payload is then encrypted using the symmetric cipher in CBC mode+using the hashed namespace as an initialization vector (IV).++The probability of detecting a namespace mismatch is thus \(1 - 2^{128-l}\)+where \(l\) is the length of the serialized payload.+-}+module Data.CryptoID.Poly+  ( CryptoID(..)+  , CryptoIDKey+  , genKey+  , encrypt+  , decrypt+  , CryptoIDError(..)+  , CryptoCipher, CryptoHash+  ) where++import Data.CryptoID++import Data.Binary+import Data.Binary.Put+import Data.Binary.Get++import Data.ByteString (ByteString)+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Char8 as ByteString.Char++import qualified Data.ByteString.Lazy as Lazy (ByteString)++import Data.List (sortOn)+import Data.Ord (Down(..))++import Data.ByteArray (ByteArrayAccess)+import qualified Data.ByteArray as ByteArray++import Data.Foldable (asum)+import Control.Monad.Except+import Control.Exception++import Data.Typeable+import GHC.TypeLits++import Crypto.Cipher.Types+import Crypto.Cipher.Blowfish (Blowfish)+import Crypto.Hash (hash, Digest)+import Crypto.Hash.Algorithms (SHAKE128)+import Crypto.Error++import Crypto.Random.Entropy+++-- | The symmetric cipher 'BlockCipher' this module uses +type CryptoCipher = Blowfish+-- | The cryptographic 'HashAlgorithm' this module uses+--+-- We expect the block size of 'CryptoCipher' to be exactly the size of the+-- 'Digest' generated by 'CryptoHash' (since a 'Digest' is used as an 'IV').+--+-- Violation of this expectation causes runtime errors.+type CryptoHash   = SHAKE128 64+  ++-- | This newtype ensures only keys of the correct length can be created+--+-- Use 'genKey' to securely generate keys.+--+-- Use the 'Binary' instance to save and restore values of 'CryptoIDKey' across+-- executions.+newtype CryptoIDKey = CryptoIDKey { keyMaterial :: ByteString }+  deriving (Typeable, ByteArrayAccess)++-- | Does not actually show any key material+instance Show CryptoIDKey where+  show = show . typeOf++instance Binary CryptoIDKey where+  put = putByteString . keyMaterial+  get = CryptoIDKey <$> getKey (cipherKeySize cipher)+    where+      cipher :: CryptoCipher+      cipher = undefined++      -- Try key sizes from large to small ('Get' commits to the first branch+      -- that parses)+      getKey (KeySizeFixed n) = getByteString n+      getKey (KeySizeEnum ns) = asum [ getKey $ KeySizeFixed n | n <- sortOn Down ns ]+      getKey (KeySizeRange min max) = getKey $ KeySizeEnum [max .. min]+++-- | Error cases that can be encountered during 'encrypt' and 'decrypt'+data CryptoIDError+  = AlgorithmError CryptoError+    -- ^ One of the underlying cryptographic algorithms+    --   ('CryptoHash' or 'CryptoCipher') failed.+  | NamespaceHashIsWrongLength ByteString+    -- ^ The length of the digest produced by 'CryptoHash' does+    --   not match the block size of 'CryptoCipher'.+    --+    -- The offending digest is included.+    --+    -- This error should not occur and is included primarily+    -- for sake of totality.+  | CiphertextConversionFailed+    -- ^ The produced 'ByteString' is the wrong length for conversion into a+    --   ciphertext.+  | DeserializationError (Lazy.ByteString, ByteOffset, String)+    -- ^ The plaintext obtained by decrypting a ciphertext with the given+    --   'CryptoIDKey' in the context of the @namespace@ could not be+    --   deserialized into a value of the expected @payload@-type.+    --+    -- This is expected behaviour if the @namespace@ or @payload@-type does not+    -- match the ones used during 'encrypt'ion or if the 'ciphertext' was+    -- tempered with.+  | InvalidNamespaceDetected+    -- ^ We have determined that, allthough deserializion succeded, the+    --   ciphertext was likely modified during transit or created using a+    --   different namespace.+  deriving (Show, Eq)++instance Exception CryptoIDError++-- | Securely generate a new key using system entropy+--+-- When 'CryptoCipher' accepts keys of varying lengths this function generates a+-- key of the largest accepted size.+genKey :: MonadIO m => m CryptoIDKey+genKey = CryptoIDKey <$> liftIO (getEntropy keySize)+  where+    keySize' = cipherKeySize (undefined :: CryptoCipher)++    keySize+      | KeySizeFixed n <- keySize' = n+      | KeySizeEnum ns <- keySize' = maximum ns+      | KeySizeRange _ max <- keySize' = max+  +  +-- | @pad err size src@ appends null bytes to @src@ until it has length that is+--   a multiple of @size@.+pad :: ByteArrayAccess a => Int -> a -> ByteString+pad n (ByteArray.unpack -> src) = ByteString.pack $ src ++ replicate (l `mod` n) 0+  where+    l = length src++-- | Use 'CryptoHash' to generate a 'Digest' of the Symbol passed as proxy type+namespace' :: forall proxy namespace m.+              ( KnownSymbol namespace, MonadError CryptoIDError m+              ) => proxy namespace -> m (IV CryptoCipher)+namespace' p = case makeIV namespaceHash of+                 Nothing -> throwError . NamespaceHashIsWrongLength $ ByteArray.convert namespaceHash+                 Just iv -> return iv+  where+    namespaceHash :: Digest CryptoHash+    namespaceHash = hash . ByteString.Char.pack $ symbolVal p++-- | Wrap failure of one of the cryptographic algorithms as a 'CryptoIDError'+cryptoFailable :: MonadError CryptoIDError m => CryptoFailable a -> m a+cryptoFailable = either (throwError . AlgorithmError) return . eitherCryptoError++-- | Encrypt an arbitrary serializable value+encrypt :: forall m namespace.+           ( KnownSymbol namespace+           , MonadError CryptoIDError m+           ) => CryptoIDKey -> ByteString -> m (CryptoID namespace ByteString)+encrypt (keyMaterial -> key) plaintext = do+  cipher <- cryptoFailable (cipherInit key :: CryptoFailable CryptoCipher)+  namespace <- namespace' (Proxy :: Proxy namespace)+  return . CryptoID . cbcEncrypt cipher namespace $ pad (blockSize cipher) plaintext+++-- | Decrypt an arbitrary serializable value+decrypt :: forall m namespace.+           ( KnownSymbol namespace+           , MonadError CryptoIDError m+           ) => CryptoIDKey -> CryptoID namespace ByteString -> m ByteString+decrypt (keyMaterial -> key) CryptoID{..} = do+  cipher <- cryptoFailable (cipherInit key :: CryptoFailable CryptoCipher)+  namespace <- namespace' (Proxy :: Proxy namespace)+  return $ cbcDecrypt cipher namespace ciphertext+