packages feed

password 3.0.0.0 → 3.0.1.0

raw patch · 4 files changed

+77/−32 lines, 4 filesdep ~bytestringdep ~cryptonitedep ~memoryPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: bytestring, cryptonite, memory

API changes (from Hackage documentation)

Files

ChangeLog.md view
@@ -1,5 +1,11 @@ # Changelog for `password` +## 3.0.1.0++-   Argon2 hashes without a version field are interpreted as being of version 1.0+    Thanks to [@Vlix](https://github.com/Vlix)+    [#56](https://github.com/cdepillabout/password/pull/56)+ ## 3.0.0.0  -   Split the main datatypes module (`Data.Password`) into a separate package: `password-types`.
password.cabal view
@@ -1,7 +1,7 @@ cabal-version: 1.12  name:           password-version:        3.0.0.0+version:        3.0.1.0 category:       Data synopsis:       Hashing and checking of passwords description:@@ -72,8 +72,8 @@       base        >= 4.9      && < 5     , base64      >= 0.3      && < 0.5     , bytestring  >= 0.10.8.1 && < 0.11-    , cryptonite  >= 0.15.1   && < 0.29-    , memory      >= 0.14     && < 0.16+    , cryptonite  >= 0.15.1   && < 0.30+    , memory      >= 0.14     && < 0.17     , password-types             < 2     , template-haskell     , text        >= 1.2.2    && < 1.3
src/Data/Password/Argon2.hs view
@@ -81,7 +81,7 @@ import Data.Semigroup ((<>)) #endif import Data.Text (Text)-import qualified Data.Text as T (intercalate, length, split, splitAt)+import qualified Data.Text as T (intercalate, split, splitAt, stripPrefix) import Data.Word (Word32)  import Data.Password.Internal (@@ -277,30 +277,45 @@ checkPassword pass (PasswordHash passHash) =   fromMaybe PasswordCheckFail $ do     let paramList = T.split (== '$') passHash-    guard $ Prelude.length paramList == 6-    let [ _,-          variantT,-          versionT,-          parametersT,-          salt64,-          hashedKey64 ] = paramList-    argon2Variant <- parseVariant variantT-    argon2Version <- parseVersion versionT-    (argon2MemoryCost, argon2TimeCost, argon2Parallelism) <- parseParameters parametersT-    salt <- from64 $ unsafePad64 salt64-    hashedKey <- from64 $ unsafePad64 hashedKey64-    let argon2OutputLength = fromIntegral $ B.length hashedKey -- only here because of warnings-        producedKey = hashPasswordWithSalt' Argon2Params{..} (Salt salt) pass+    (argon2Params, salt, hashedKey) <- parseArgon2Params paramList+    let producedKey = hashPasswordWithSalt' argon2Params salt pass     guard $ hashedKey `constEq` producedKey     return PasswordCheckSuccess++parseArgon2Params :: [Text] -> Maybe (Argon2Params, Salt Argon2, ByteString)+-- vp - version or params+-- ps - params or salt+-- sh - salt or hash+parseArgon2Params (_:variantT:vp:ps:sh:rest) = do+    variant <- parseVariant variantT+    case rest of+        -- If there is a 6th part, we'll assume the version is included+        [hashedKey64] -> do+            version <- parseVersion vp+            parseAll variant version ps sh hashedKey64+        -- If there are only 5 parts, we'll assume the version is 'Version10'+        [] -> parseAll variant Version10 vp ps sh+        -- Any other amount of parts means the provided hash is malformed+        _ -> Nothing   where-    argon2Salt = 16 -- only here because of warnings     parseVariant = splitMaybe "argon2" letterToVariant     parseVersion = splitMaybe "v=" numToVersion-    parseParameters params = do-        let ps = T.split (== ',') params-        guard $ Prelude.length ps == 3-        go ps (Nothing, Nothing, Nothing)+-- If there are less than 5 parts, the hash is malformed+parseArgon2Params _ = Nothing++parseAll :: Argon2.Variant -> Argon2.Version -> Text -> Text -> Text -> Maybe (Argon2Params, Salt Argon2, ByteString)+parseAll argon2Variant argon2Version parametersT salt64 hashedKey64 = do+    (argon2MemoryCost, argon2TimeCost, argon2Parallelism) <- parseParameters parametersT+    salt <- from64 $ unsafePad64 salt64+    hashedKey <- from64 $ unsafePad64 hashedKey64+    let argon2OutputLength = fromIntegral $ B.length hashedKey -- only here because of warnings+        argon2Salt = 16 -- only here because of warnings+    pure (Argon2Params{..}, Salt salt, hashedKey)+  where+    parseParameters paramsT = do+        let paramsL = T.split (== ',') paramsT+        guard $ Prelude.length paramsL == 3+        go paramsL (Nothing, Nothing, Nothing)       where         go [] (Just m, Just t, Just p) = Just (m, t, p)         go [] _ = Nothing@@ -310,11 +325,12 @@             ("t=", i) -> go xs (m, readT i, p)             ("p=", i) -> go xs (m, t, readT i)             _ -> Nothing-    splitMaybe :: Text -> (Text -> Maybe a) -> Text -> Maybe a-    splitMaybe match f t =-      case T.splitAt (T.length match) t of-        (m, x) | m == match -> f x-        _  -> Nothing++-- | Strips the given 'match' if it matches and uses+--   the function on the remainder of the given text.+splitMaybe :: Text -> (Text -> Maybe a) -> Text -> Maybe a+splitMaybe match f t =+    T.stripPrefix match t >>= f  -- | Generate a random 16-byte @Argon2@ salt --
test/tasty/Argon2.hs view
@@ -1,7 +1,7 @@ {-# LANGUAGE OverloadedStrings #-}-module Argon2 where+module Argon2 (testArgon2) where -import Test.Tasty+import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (assertBool, assertEqual, testCase)  import Data.Password.Argon2@@ -20,6 +20,7 @@   , testWithParams "Argon2 (Argon2i)" $ fastParams{ argon2Variant = Argon2i }   , testWithParams "Argon2 (Argon2d)" $ fastParams{ argon2Variant = Argon2d }   , paddingTests+  , omittedVersionTest   ]   where     testWithParams s params =@@ -47,14 +48,28 @@ hashWithPadding    = PasswordHash "$argon2id$v=19$m=65536,t=2,p=1$YWJjZGVmZ2hpamtsbW5vcA==$BztdyfEefG5V18ZNlztPrfZaU5duVFKZiI6dJeWht0o=" hashWithoutPadding = PasswordHash "$argon2id$v=19$m=65536,t=2,p=1$YWJjZGVmZ2hpamtsbW5vcA$BztdyfEefG5V18ZNlztPrfZaU5duVFKZiI6dJeWht0o" +-- Very old hashes might not have version parts, so infer as version 1.0+omittedVersionTest :: TestTree+omittedVersionTest = testGroup "Version 1.0"+    [ go "version 1.0 part in hash (placebo)" "testtest" v10Hash+    , go "no version part in hash == version 1.0" "testtest" v10HashWithoutVersion+    , go "version 1.3 part in hash (reference)" "password" referenceHash+    , testCase "no version 1.3 part in hash should fail" $+        assertEqual "check passed!?" PasswordCheckFail $+            checkPassword "password" referenceHashWithoutVersion+    ]+  where+    go s p = testCase s+        . assertEqual "check failed" PasswordCheckSuccess+        . checkPassword p+ -- Reference check using the Command-line Utility output example -- from: https://github.com/P-H-C/phc-winner-argon2 referenceTest :: TestTree referenceTest = testCase "PHC Argon2 reference" $-    assertEqual "output hash is wrong" expected $+    assertEqual "output hash is wrong" referenceHash $         hashPasswordWithSalt params salt pwd   where-    expected = PasswordHash "$argon2i$v=19$m=65536,t=2,p=4$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJObG"     salt = Salt "somesalt"     pwd = mkPassword "password"     params = defaultParams {@@ -62,3 +77,11 @@         argon2Parallelism = 4,         argon2OutputLength = 24     }++-- Weirdly lined out to show it's exactly the same, except the 'v=' part is missing.+referenceHash, referenceHashWithoutVersion :: PasswordHash Argon2+referenceHash          = PasswordHash "$argon2i$v=19$m=65536,t=2,p=4$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJObG"+referenceHashWithoutVersion = PasswordHash "$argon2i$m=65536,t=2,p=4$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJObG"+v10Hash, v10HashWithoutVersion :: PasswordHash Argon2+v10Hash          = PasswordHash "$argon2i$v=16$m=65536,t=2,p=1$Kx1BEcpIg0Ey5GyXq5do2w$0qRfWHw09EdqQkSsaG57O/ou8v/E6Vc83w"+v10HashWithoutVersion = PasswordHash "$argon2i$m=65536,t=2,p=1$Kx1BEcpIg0Ey5GyXq5do2w$0qRfWHw09EdqQkSsaG57O/ou8v/E6Vc83w"