packages feed

password-types 1.0.0.0 → 1.0.1.0

raw patch · 4 files changed

+90/−20 lines, 4 filesdep −QuickCheckdep −memorydep −template-haskellPVP ok

version bump matches the API change (PVP)

Dependencies removed: QuickCheck, memory, template-haskell

API changes (from Hackage documentation)

+ Data.Password.Types: constEquals :: ByteString -> ByteString -> Bool

Files

ChangeLog.md view
@@ -1,5 +1,11 @@ # Changelog for `password-types` +## 1.0.1.0++-   Removed `memory` dependency by implementing `constEq` in this package.+-   Exporting `constEquals` for reuse in other packages to minimize dependencies+    on `memory` or `ram`.+ ## 1.0.0.0  -   Split out this package from the `password` package to not saddle up
password-types.cabal view
@@ -1,8 +1,8 @@ cabal-version: 1.12  name:           password-types-version:        1.0.0.0-category:       Data+version:        1.0.1.0+category:       Security synopsis:       Types for handling passwords description:    A library providing types for working with plain-text and hashed passwords. homepage:       https://github.com/cdepillabout/password/tree/master/password-types#readme@@ -12,6 +12,8 @@ copyright:      Copyright (c) Dennis Gosnell & Felix Paulusma, 2020 license:        BSD3 license-file:   LICENSE+tested-with:    GHC == 9.4.8, GHC == 9.6.7, GHC == 9.8.4,+                GHC == 9.10.3, GHC == 9.12.4, GHC == 9.14.1 build-type:     Custom extra-source-files:     README.md@@ -19,9 +21,9 @@  custom-setup   setup-depends:-      base-    , Cabal-    , cabal-doctest  >=1.0.6 && <1.1+      base           >= 4.9   && < 5+    , Cabal                      < 4+    , cabal-doctest  >= 1.0.6 && < 1.1  source-repository head   type: git@@ -37,8 +39,7 @@   build-depends:       base        >= 4.9 && < 5     , bytestring            < 1-    , memory                < 0.16-    , text                  < 1.3+    , text                  < 3   ghc-options:       -Wall   default-language:@@ -54,13 +55,9 @@   ghc-options:       -threaded -O2 -rtsopts -with-rtsopts=-N   build-depends:-      base >=4.9 && <5+      base >= 4.9 && < 5     , base-compat     , doctest-    , password-types-    , QuickCheck-    , quickcheck-instances-    , template-haskell   default-language:       Haskell2010 @@ -76,7 +73,7 @@   ghc-options:       -threaded -O2 -rtsopts -with-rtsopts=-N   build-depends:-      base >=4.9 && <5+      base >= 4.9 && < 5     , password-types     , quickcheck-instances     , tasty
src/Data/Password/Types.hs view
@@ -1,4 +1,6 @@+{-# LANGUAGE BangPatterns #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}+ {-| Module      : Data.Password.Types Copyright   : (c) Dennis Gosnell, 2019; Felix Paulusma, 2020@@ -48,14 +50,27 @@   , unsafeShowPassword     -- * Hashing salts   , Salt (..)+    -- * Utility functions++    -- | These functions might not be specific to passwords, but+    -- can be useful when handling them.+  , constEquals   ) where -import Data.ByteArray (constEq)-import Data.ByteString (ByteString)+import Data.ByteString.Internal (ByteString (..)) import Data.Function (on) import Data.String (IsString(..)) import Data.Text (Text) import Data.Text.Encoding (encodeUtf8)+import Foreign (+  Word8,+  Ptr,+  Bits ((.|.), xor),+  peekByteOff,+  plusPtr,+  withForeignPtr,+ )+import System.IO.Unsafe (unsafeDupablePerformIO)  -- $setup -- >>> :set -XOverloadedStrings@@ -112,9 +127,46 @@   } deriving (Ord, Read, Show)  instance Eq (PasswordHash a)  where-  (==) = constEq `on` encodeUtf8 . unPasswordHash+  (==) = constEquals `on` encodeUtf8 . unPasswordHash  -- | A salt used by a hashing algorithm. newtype Salt a = Salt   { getSalt :: ByteString   } deriving (Eq, Show)++-- The below is somewhat copied over from the 'memory'(/ram) package(s)++-- | Checking two 'ByteString's for equality without short-circuiting on the+-- first byte that is different. This is used in the definition of '==' for+-- 'PasswordHash'es, to mitigate timing attacks.+--+-- It _will_ give an early 'False' if the length of the 'ByteString's aren't+-- the same, but this does not help in timing attacks since that means the+-- comparison is being done between two hashes of different hash functions.+-- Which only happens if the implementation is comparing the wrong hashes.+constEquals :: ByteString -> ByteString -> Bool+constEquals (PS fptr1 off1 l1) (PS fptr2 off2 l2)+  -- This is used to compare hashes of passwords, which should be equal length+  -- if they compare the same type of algorithm with the same settings, so it's+  -- fine to do an early return on bad hash comparisons.+  | l1 /= l2 = False+  | otherwise =+      unsafeDupablePerformIO $+        withForeignPtr fptr1 $ \ptr1 ->+          withForeignPtr fptr2 $ \ptr2 ->+            -- Using the 'offset' for backwards compatibility (bytestring < 0.11)+            memConstEqual (ptr1 `plusPtr` off1) (ptr2 `plusPtr` off2) l1++-- | This function MUST take two memory buffers of equal length,+-- or it will have undefined behaviour.+memConstEqual :: Ptr Word8 -> Ptr Word8 -> Int -> IO Bool+memConstEqual p1 p2 n =+    loop 0 0+  where+    loop :: Int -> Word8 -> IO Bool+    loop i !acc+      | i == n = pure $! acc == 0+      | otherwise = do+            w1 <- peekByteOff p1 i+            w2 <- peekByteOff p2 i+            loop (i + 1) (acc .|. xor w1 w2) 
test/tasty/Spec.hs view
@@ -2,11 +2,17 @@  import Data.String (fromString) import Data.Text (pack)-import Test.QuickCheck.Instances()-import Test.Tasty ( defaultMain, testGroup )-import Test.Tasty.QuickCheck ( (===), testProperty )+import Data.Text.Encoding (encodeUtf8)+import Test.QuickCheck.Instances ()+import Test.Tasty (defaultMain, testGroup)+import Test.Tasty.QuickCheck ((===), testProperty) -import Data.Password.Types ( mkPassword, unsafeShowPassword )+import Data.Password.Types (+    PasswordHash (..),+    constEquals,+    mkPassword,+    unsafeShowPassword,+ )  main :: IO () main = defaultMain $@@ -17,4 +23,13 @@         show (mkPassword pass) === "**PASSWORD**"     , testProperty "fromString works" $ \pass ->         unsafeShowPassword (fromString pass) === pack pass+    , testProperty "constEquals works identical to '=='" $ \t1 t2 ->+        let b1 = encodeUtf8 t1+            b2 = encodeUtf8 t2+         in (b1 `constEquals` b2) == (b1 == b2) +    , testProperty "constEquals is True on the same input" $ \t1 ->+        let b1 = encodeUtf8 t1+         in b1 `constEquals` b1+    , testProperty "comparing 'PasswordHash'es works" $ \t1 ->+        PasswordHash t1 === PasswordHash t1     ]