diff options
-rw-r--r-- | default.nix | 8 | ||||
-rw-r--r-- | hmacaroons.cabal | 2 | ||||
-rw-r--r-- | src/Crypto/Macaroon/Verifier.hs | 73 | ||||
-rw-r--r-- | test/Crypto/Macaroon/Verifier/Tests.hs | 59 |
4 files changed, 42 insertions, 100 deletions
diff --git a/default.nix b/default.nix index d968974..bd725a9 100644 --- a/default.nix +++ b/default.nix | |||
@@ -1,6 +1,6 @@ | |||
1 | { mkDerivation, attoparsec, base, base64-bytestring, byteable | 1 | { mkDerivation, attoparsec, base, base64-bytestring, byteable |
2 | , bytestring, cereal, cryptohash, deepseq, hex, QuickCheck, stdenv | 2 | , bytestring, cereal, cryptohash, deepseq, either, hex, QuickCheck |
3 | , tasty, tasty-hunit, tasty-quickcheck | 3 | , stdenv, tasty, tasty-hunit, tasty-quickcheck |
4 | }: | 4 | }: |
5 | mkDerivation { | 5 | mkDerivation { |
6 | pname = "hmacaroons"; | 6 | pname = "hmacaroons"; |
@@ -8,11 +8,11 @@ mkDerivation { | |||
8 | src = ./.; | 8 | src = ./.; |
9 | buildDepends = [ | 9 | buildDepends = [ |
10 | attoparsec base base64-bytestring byteable bytestring cereal | 10 | attoparsec base base64-bytestring byteable bytestring cereal |
11 | cryptohash deepseq hex | 11 | cryptohash deepseq either hex |
12 | ]; | 12 | ]; |
13 | testDepends = [ | 13 | testDepends = [ |
14 | attoparsec base base64-bytestring byteable bytestring cereal | 14 | attoparsec base base64-bytestring byteable bytestring cereal |
15 | cryptohash hex QuickCheck tasty tasty-hunit tasty-quickcheck | 15 | cryptohash either hex QuickCheck tasty tasty-hunit tasty-quickcheck |
16 | ]; | 16 | ]; |
17 | homepage = "https://github.com/jtanguy/hmacaroons"; | 17 | homepage = "https://github.com/jtanguy/hmacaroons"; |
18 | description = "Haskell implementation of macaroons"; | 18 | description = "Haskell implementation of macaroons"; |
diff --git a/hmacaroons.cabal b/hmacaroons.cabal index b70a984..3aa338a 100644 --- a/hmacaroons.cabal +++ b/hmacaroons.cabal | |||
@@ -86,6 +86,7 @@ benchmark bench | |||
86 | cereal >= 0.4, | 86 | cereal >= 0.4, |
87 | cryptohash >=0.11 && <0.12, | 87 | cryptohash >=0.11 && <0.12, |
88 | -- cipher-aes >=0.2 && <0.3, | 88 | -- cipher-aes >=0.2 && <0.3, |
89 | either >=4.4, | ||
89 | hex >= 0.1, | 90 | hex >= 0.1, |
90 | deepseq >= 1.1, | 91 | deepseq >= 1.1, |
91 | criterion >= 1.1 | 92 | criterion >= 1.1 |
@@ -102,6 +103,7 @@ test-suite test | |||
102 | byteable >= 0.1 && <0.2, | 103 | byteable >= 0.1 && <0.2, |
103 | cereal >= 0.4, | 104 | cereal >= 0.4, |
104 | cryptohash >=0.11 && <0.12, | 105 | cryptohash >=0.11 && <0.12, |
106 | either >=4.4, | ||
105 | hex >= 0.1, | 107 | hex >= 0.1, |
106 | tasty >= 0.10, | 108 | tasty >= 0.10, |
107 | tasty-hunit >= 0.9, | 109 | tasty-hunit >= 0.9, |
diff --git a/src/Crypto/Macaroon/Verifier.hs b/src/Crypto/Macaroon/Verifier.hs index 02cb448..713a971 100644 --- a/src/Crypto/Macaroon/Verifier.hs +++ b/src/Crypto/Macaroon/Verifier.hs | |||
@@ -1,5 +1,5 @@ | |||
1 | {-# LANGUAGE OverloadedStrings #-} | 1 | {-# LANGUAGE OverloadedStrings #-} |
2 | {-# LANGUAGE RankNTypes #-} | 2 | {-# LANGUAGE RankNTypes #-} |
3 | {-| | 3 | {-| |
4 | Module : Crypto.Macaroon.Verifier | 4 | Module : Crypto.Macaroon.Verifier |
5 | Copyright : (c) 2015 Julien Tanguy | 5 | Copyright : (c) 2015 Julien Tanguy |
@@ -13,79 +13,50 @@ Portability : portable | |||
13 | 13 | ||
14 | -} | 14 | -} |
15 | module Crypto.Macaroon.Verifier ( | 15 | module Crypto.Macaroon.Verifier ( |
16 | Verified(..) | 16 | Verifier |
17 | , CaveatVerifier | ||
18 | , (<???>) | ||
19 | , verifyMacaroon | 17 | , verifyMacaroon |
20 | , verifySig | 18 | , verifySig |
21 | , verifyExact | 19 | -- , verifyExact |
22 | , verifyFun | 20 | -- , verifyFun |
23 | , module Data.Attoparsec.ByteString.Char8 | 21 | , module Data.Attoparsec.ByteString.Char8 |
24 | , verifyCavs | 22 | , verifyCavs |
25 | ) where | 23 | ) where |
26 | 24 | ||
27 | 25 | ||
28 | import Crypto.Hash | 26 | import Crypto.Hash |
27 | import Data.Attoparsec.ByteString | ||
28 | import Data.Attoparsec.ByteString.Char8 | ||
29 | import Data.Bool | 29 | import Data.Bool |
30 | import qualified Data.ByteString as BS | ||
31 | import Data.Byteable | 30 | import Data.Byteable |
31 | import qualified Data.ByteString as BS | ||
32 | import Data.Either | ||
33 | import Data.Either.Validation | ||
32 | import Data.Foldable | 34 | import Data.Foldable |
33 | import Data.Function | 35 | import Data.Function |
34 | import Data.Maybe | 36 | import Data.Maybe |
35 | import Data.Traversable | 37 | import Data.Traversable |
36 | import Data.Attoparsec.ByteString | ||
37 | import Data.Attoparsec.ByteString.Char8 | ||
38 | 38 | ||
39 | import Crypto.Macaroon.Internal | 39 | import Crypto.Macaroon.Internal |
40 | 40 | ||
41 | type Verifier = Caveat -> Maybe (Either String Caveat) | ||
41 | 42 | ||
42 | -- | Opaque datatype for now. Might need more explicit errors | 43 | verifySig :: Key -> Macaroon -> Either String Macaroon |
43 | data Verified = Ok | Failed deriving (Show,Eq) | 44 | verifySig k m = bool (Left "Signatures do not match") (Right m) $ |
44 | |||
45 | instance Monoid Verified where | ||
46 | mempty = Ok | ||
47 | mappend Ok Ok = Ok | ||
48 | mappend _ _ = Failed | ||
49 | |||
50 | |||
51 | data CaveatVerifier = CV { vFun :: Caveat -> Maybe Verified , helpText :: String} | ||
52 | |||
53 | instance Eq CaveatVerifier where | ||
54 | (==) = (==) `on` helpText | ||
55 | |||
56 | instance Show CaveatVerifier where | ||
57 | show = helpText | ||
58 | |||
59 | (<???>) :: (Caveat -> Maybe Verified) -> String -> CaveatVerifier | ||
60 | f <???> t = CV f t | ||
61 | |||
62 | verifySig :: Key -> Macaroon -> Verified | ||
63 | verifySig k m = bool Failed Ok $ | ||
64 | signature m == foldl' hash (toBytes (hmac derivedKey (identifier m) :: HMAC SHA256)) (caveats m) | 45 | signature m == foldl' hash (toBytes (hmac derivedKey (identifier m) :: HMAC SHA256)) (caveats m) |
65 | where | 46 | where |
66 | hash s c = toBytes (hmac s (vid c `BS.append` cid c) :: HMAC SHA256) | 47 | hash s c = toBytes (hmac s (vid c `BS.append` cid c) :: HMAC SHA256) |
67 | derivedKey = toBytes (hmac "macaroons-key-generator" k :: HMAC SHA256) | 48 | derivedKey = toBytes (hmac "macaroons-key-generator" k :: HMAC SHA256) |
68 | 49 | ||
69 | verifyMacaroon :: Key -> [CaveatVerifier] -> Macaroon -> Verified | 50 | verifyMacaroon :: Key -> [Verifier] -> Macaroon -> Either String Macaroon |
70 | verifyMacaroon secret verifiers m = verifySig secret m `mappend` verifyCavs verifiers m | 51 | verifyMacaroon secret verifiers m = verifySig secret m >>= verifyCavs verifiers |
71 | |||
72 | 52 | ||
73 | verifyCavs :: [CaveatVerifier] -> Macaroon -> Verified | 53 | verifyCavs :: [Verifier] -> Macaroon -> Either String Macaroon |
74 | verifyCavs verifiers m = foldMap (\c -> fromMaybe Failed $ foldMap (($ c) . vFun) verifiers) (caveats m) | 54 | verifyCavs verifiers m = case partitionEithers verifiedCaveats of |
55 | ([],_) -> Right m | ||
56 | (errs,_) -> Left (mconcat errs) | ||
57 | where | ||
58 | verifiedCaveats = map (\c -> defaultFail c $ foldMap (fmap eitherToValidation . ($c)) verifiers) $ caveats m | ||
59 | defaultFail c = maybe (Left ("No validation for this caveat: " ++ show c)) validationToEither | ||
75 | 60 | ||
76 | verifyExact :: (Eq a) => Key -> a -> Parser a -> Caveat -> Maybe Verified | ||
77 | verifyExact k expected = verifyFun k (expected ==) | ||
78 | 61 | ||
79 | verifyFun :: Key -> (a -> Bool) -> Parser a -> Caveat -> Maybe Verified | 62 | -- TODO: define API |
80 | verifyFun key f parser cav = if key `BS.isPrefixOf` cid cav then | ||
81 | case parseOnly kvparser (cid cav) of | ||
82 | Right v -> (bool Failed Ok . f) <$> Just v | ||
83 | Left _ -> Just Failed | ||
84 | else Nothing | ||
85 | where | ||
86 | kvparser = do | ||
87 | key <- string key | ||
88 | skipSpace | ||
89 | string "=" | ||
90 | skipSpace | ||
91 | parser <* endOfInput | ||
diff --git a/test/Crypto/Macaroon/Verifier/Tests.hs b/test/Crypto/Macaroon/Verifier/Tests.hs index 101fa26..4a9295f 100644 --- a/test/Crypto/Macaroon/Verifier/Tests.hs +++ b/test/Crypto/Macaroon/Verifier/Tests.hs | |||
@@ -16,7 +16,8 @@ import Data.List | |||
16 | import qualified Data.ByteString.Char8 as B8 | 16 | import qualified Data.ByteString.Char8 as B8 |
17 | import Test.Tasty | 17 | import Test.Tasty |
18 | -- import Test.Tasty.HUnit | 18 | -- import Test.Tasty.HUnit |
19 | import Test.Tasty.QuickCheck | 19 | import Test.Tasty.QuickCheck hiding (Success, Failure) |
20 | import Data.Either | ||
20 | 21 | ||
21 | import Crypto.Macaroon | 22 | import Crypto.Macaroon |
22 | import Crypto.Macaroon.Verifier | 23 | import Crypto.Macaroon.Verifier |
@@ -25,7 +26,6 @@ import Crypto.Macaroon.Instances | |||
25 | 26 | ||
26 | tests :: TestTree | 27 | tests :: TestTree |
27 | tests = testGroup "Crypto.Macaroon.Verifier" [ sigs | 28 | tests = testGroup "Crypto.Macaroon.Verifier" [ sigs |
28 | , firstParty | ||
29 | ] | 29 | ] |
30 | 30 | ||
31 | {- | 31 | {- |
@@ -45,52 +45,21 @@ m2 = addFirstPartyCaveat "test = caveat" m | |||
45 | m3 :: Macaroon | 45 | m3 :: Macaroon |
46 | m3 = addFirstPartyCaveat "value = 42" m2 | 46 | m3 = addFirstPartyCaveat "value = 42" m2 |
47 | 47 | ||
48 | exTC = verifyExact "test" "caveat" (many' letter_ascii) <???> "test = caveat" | 48 | -- exTC = verifyExact "test" "caveat" (many' letter_ascii) |
49 | exTZ = verifyExact "test" "bleh" (many' letter_ascii) <???> "test = bleh" | 49 | -- exTZ = verifyExact "test" "bleh" (many' letter_ascii) |
50 | exV42 = verifyExact "value" 42 decimal <???> "value = 42" | 50 | -- exV42 = verifyExact "value" 42 decimal |
51 | exV43 = verifyExact "value" 43 decimal <???> "value = 43" | 51 | -- exV43 = verifyExact "value" 43 decimal |
52 | 52 | ||
53 | funTCPre = verifyFun "test" ("cav" `isPrefixOf`) (many' letter_ascii) <???> "test startsWith cav" | 53 | -- funTCPre = verifyFun "test" (string "test = " *> many' letter_ascii) |
54 | funTV43lte = verifyFun "value" (<= 43) decimal <???> "value <= 43" | 54 | -- (\e -> if "cav" `isPrefixOf` e then Right e else Left "Does not start with cav" ) |
55 | -- funTV43lte = verifyFun "value" (string "value = " *> decimal) | ||
56 | -- (\v -> if v <= 43 then Right v else Left "Greater than 43") | ||
55 | 57 | ||
56 | allvs = [exTC, exTZ, exV42, exV43, funTCPre, funTV43lte] | 58 | -- allvs = [exTC, exTZ, exV42, exV43, funTCPre, funTV43lte] |
57 | 59 | ||
58 | {- | 60 | {- |
59 | - Tests | 61 | - Tests |
60 | -} | 62 | -} |
61 | sigs = testProperty "Signatures" $ \sm -> verifySig (secret sm) (macaroon sm) == Ok | 63 | sigs = testProperty "Signatures" $ \sm -> verifySig (secret sm) (macaroon sm) == Right (macaroon sm) |
62 | 64 | ||
63 | firstParty = testGroup "First party caveats" [ | 65 | -- TODO: Re-do tests |
64 | testGroup "Pure verifiers" [ | ||
65 | testProperty "Zero caveat" $ | ||
66 | forAll (sublistOf allvs) (\vs -> Ok == verifyCavs vs m) | ||
67 | , testProperty "One caveat" $ | ||
68 | forAll (sublistOf allvs) (\vs -> disjoin [ | ||
69 | Ok == verifyCavs vs m2 .&&. any (`elem` vs) [exTC,funTCPre] .&&. (exTZ `notElem` vs) | ||
70 | , Failed === verifyCavs vs m2 | ||
71 | ]) | ||
72 | , testProperty "Two Exact" $ | ||
73 | forAll (sublistOf allvs) (\vs -> disjoin [ | ||
74 | Ok == verifyCavs vs m3 .&&. | ||
75 | any (`elem` vs) [exTC,funTCPre] .&&. (exTZ `notElem` vs) .&&. | ||
76 | any (`elem` vs) [exV42,funTV43lte] .&&. (exV43 `notElem` vs) | ||
77 | , Failed === verifyCavs vs m3 | ||
78 | ]) | ||
79 | ] | ||
80 | , testGroup "Pure verifiers with sig" [ | ||
81 | testProperty "Zero caveat" $ | ||
82 | forAll (sublistOf allvs) (\vs -> Ok == verifyMacaroon sec vs m) | ||
83 | , testProperty "One caveat" $ | ||
84 | forAll (sublistOf allvs) (\vs -> disjoin [ | ||
85 | Ok == verifyMacaroon sec vs m2 .&&. any (`elem` vs) [exTC,funTCPre] .&&. (exTZ `notElem` vs) | ||
86 | , Failed === verifyMacaroon sec vs m2 | ||
87 | ]) | ||
88 | , testProperty "Two Exact" $ | ||
89 | forAll (sublistOf allvs) (\vs -> disjoin [ | ||
90 | Ok == verifyMacaroon sec vs m3 .&&. | ||
91 | any (`elem` vs) [exTC,funTCPre] .&&. (exTZ `notElem` vs) .&&. | ||
92 | any (`elem` vs) [exV42,funTV43lte] .&&. (exV43 `notElem` vs) | ||
93 | , Failed === verifyMacaroon sec vs m3 | ||
94 | ]) | ||
95 | ] | ||
96 | ] | ||