aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJulien Tanguy <julien.tanguy@jhome.fr>2015-06-17 17:17:36 +0200
committerJulien Tanguy <julien.tanguy@jhome.fr>2015-06-17 17:17:36 +0200
commitdfcc13bda0f07f012e385d39ea86d7c6e9f9e05f (patch)
tree3bf3c0104c4051aa94b8cce5b667b0b19a6c6d5b
parent8fad4fa9d1b592ece4806dcd9abb6c011d3948bf (diff)
parent62576139b8dbf2cd0d3c04e927b9df2d0805a199 (diff)
downloadhmacaroons-dfcc13bda0f07f012e385d39ea86d7c6e9f9e05f.tar.gz
hmacaroons-dfcc13bda0f07f012e385d39ea86d7c6e9f9e05f.tar.zst
hmacaroons-dfcc13bda0f07f012e385d39ea86d7c6e9f9e05f.zip
Add basic macaroon verification
-rw-r--r--src/Crypto/Macaroon/Internal.hs2
-rw-r--r--src/Crypto/Macaroon/Verifier.hs65
-rw-r--r--test/Crypto/Macaroon/Instances.hs21
-rw-r--r--test/Crypto/Macaroon/Serializer/Base64/Tests.hs2
-rw-r--r--test/Crypto/Macaroon/Verifier/Tests.hs79
5 files changed, 141 insertions, 28 deletions
diff --git a/src/Crypto/Macaroon/Internal.hs b/src/Crypto/Macaroon/Internal.hs
index 116f5ed..2f56512 100644
--- a/src/Crypto/Macaroon/Internal.hs
+++ b/src/Crypto/Macaroon/Internal.hs
@@ -58,7 +58,7 @@ instance Show Macaroon where
58 show (MkMacaroon l i c s) = intercalate "\n" [ 58 show (MkMacaroon l i c s) = intercalate "\n" [
59 "location " ++ B8.unpack l 59 "location " ++ B8.unpack l
60 , "identifier " ++ B8.unpack i 60 , "identifier " ++ B8.unpack i
61 , concatMap show c 61 , intercalate "\n" (map show c)
62 , "signature " ++ B8.unpack (hex s) 62 , "signature " ++ B8.unpack (hex s)
63 ] 63 ]
64 64
diff --git a/src/Crypto/Macaroon/Verifier.hs b/src/Crypto/Macaroon/Verifier.hs
index 0d1636c..02cb448 100644
--- a/src/Crypto/Macaroon/Verifier.hs
+++ b/src/Crypto/Macaroon/Verifier.hs
@@ -1,4 +1,5 @@
1{-# LANGUAGE OverloadedStrings #-} 1{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE RankNTypes #-}
2{-| 3{-|
3Module : Crypto.Macaroon.Verifier 4Module : Crypto.Macaroon.Verifier
4Copyright : (c) 2015 Julien Tanguy 5Copyright : (c) 2015 Julien Tanguy
@@ -11,7 +12,17 @@ Portability : portable
11 12
12 13
13-} 14-}
14module Crypto.Macaroon.Verifier where 15module Crypto.Macaroon.Verifier (
16 Verified(..)
17 , CaveatVerifier
18 , (<???>)
19 , verifyMacaroon
20 , verifySig
21 , verifyExact
22 , verifyFun
23 , module Data.Attoparsec.ByteString.Char8
24 , verifyCavs
25) where
15 26
16 27
17import Crypto.Hash 28import Crypto.Hash
@@ -19,16 +30,62 @@ import Data.Bool
19import qualified Data.ByteString as BS 30import qualified Data.ByteString as BS
20import Data.Byteable 31import Data.Byteable
21import Data.Foldable 32import Data.Foldable
33import Data.Function
34import Data.Maybe
35import Data.Traversable
36import Data.Attoparsec.ByteString
37import Data.Attoparsec.ByteString.Char8
22 38
23import Crypto.Macaroon.Internal 39import Crypto.Macaroon.Internal
24 40
25 41
26-- | Opaque datatype for now. Might need more explicit errors 42-- | Opaque datatype for now. Might need more explicit errors
27data Result = Success | Failure deriving (Show,Eq) 43data Verified = Ok | Failed deriving (Show,Eq)
28 44
29verifySig :: Key -> Macaroon -> Result 45instance Monoid Verified where
30verifySig k m = bool Failure Success $ 46 mempty = Ok
47 mappend Ok Ok = Ok
48 mappend _ _ = Failed
49
50
51data CaveatVerifier = CV { vFun :: Caveat -> Maybe Verified , helpText :: String}
52
53instance Eq CaveatVerifier where
54 (==) = (==) `on` helpText
55
56instance Show CaveatVerifier where
57 show = helpText
58
59(<???>) :: (Caveat -> Maybe Verified) -> String -> CaveatVerifier
60f <???> t = CV f t
61
62verifySig :: Key -> Macaroon -> Verified
63verifySig k m = bool Failed Ok $
31 signature m == foldl' hash (toBytes (hmac derivedKey (identifier m) :: HMAC SHA256)) (caveats m) 64 signature m == foldl' hash (toBytes (hmac derivedKey (identifier m) :: HMAC SHA256)) (caveats m)
32 where 65 where
33 hash s c = toBytes (hmac s (vid c `BS.append` cid c) :: HMAC SHA256) 66 hash s c = toBytes (hmac s (vid c `BS.append` cid c) :: HMAC SHA256)
34 derivedKey = toBytes (hmac "macaroons-key-generator" k :: HMAC SHA256) 67 derivedKey = toBytes (hmac "macaroons-key-generator" k :: HMAC SHA256)
68
69verifyMacaroon :: Key -> [CaveatVerifier] -> Macaroon -> Verified
70verifyMacaroon secret verifiers m = verifySig secret m `mappend` verifyCavs verifiers m
71
72
73verifyCavs :: [CaveatVerifier] -> Macaroon -> Verified
74verifyCavs verifiers m = foldMap (\c -> fromMaybe Failed $ foldMap (($ c) . vFun) verifiers) (caveats m)
75
76verifyExact :: (Eq a) => Key -> a -> Parser a -> Caveat -> Maybe Verified
77verifyExact k expected = verifyFun k (expected ==)
78
79verifyFun :: Key -> (a -> Bool) -> Parser a -> Caveat -> Maybe Verified
80verifyFun 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/Instances.hs b/test/Crypto/Macaroon/Instances.hs
index 4e2f39f..c82bbd3 100644
--- a/test/Crypto/Macaroon/Instances.hs
+++ b/test/Crypto/Macaroon/Instances.hs
@@ -26,9 +26,16 @@ import Crypto.Macaroon
26 26
27-- | Adjust the size parameter, by transforming it with the given 27-- | Adjust the size parameter, by transforming it with the given
28-- function. 28-- function.
29-- Copied over from QuickCheck 2.8
29scale :: (Int -> Int) -> Gen a -> Gen a 30scale :: (Int -> Int) -> Gen a -> Gen a
30scale f g = sized (\n -> resize (f n) g) 31scale f g = sized (\n -> resize (f n) g)
31 32
33
34-- | Generates a random subsequence of the given list.
35-- Copied over from QuickCheck 2.8
36sublistOf :: [a] -> Gen [a]
37sublistOf = filterM (\_ -> choose (False, True))
38
32newtype Url = Url { unUrl :: BS.ByteString } deriving (Show) 39newtype Url = Url { unUrl :: BS.ByteString } deriving (Show)
33 40
34instance Arbitrary Url where 41instance Arbitrary Url where
@@ -48,6 +55,16 @@ newtype Identifier = Identifier { unIdent :: BS.ByteString } deriving (Show)
48instance Arbitrary Identifier where 55instance Arbitrary Identifier where
49 arbitrary = Identifier . B8.pack <$>(scale (*3) . listOf1 . elements $ ['a'..'z']) 56 arbitrary = Identifier . B8.pack <$>(scale (*3) . listOf1 . elements $ ['a'..'z'])
50 57
58newtype EquationLike = EquationLike { unEqlike :: BS.ByteString } deriving (Show)
59
60instance Arbitrary EquationLike where
61 arbitrary = do
62 keylen <- choose (3,8)
63 key <- B8.pack <$> vectorOf keylen (elements ['a'..'z'])
64 val <- B8.pack <$> (scale (*3) . listOf1 . elements $ ['a'..'z'])
65 return $ EquationLike (BS.concat [ key, " = ", val])
66
67
51data SimpleMac = SimpleMac { secret :: BS.ByteString, macaroon :: Macaroon } deriving Show 68data SimpleMac = SimpleMac { secret :: BS.ByteString, macaroon :: Macaroon } deriving Show
52 69
53instance Arbitrary SimpleMac where 70instance Arbitrary SimpleMac where
@@ -55,6 +72,8 @@ instance Arbitrary SimpleMac where
55 secret <- unSecret <$> arbitrary 72 secret <- unSecret <$> arbitrary
56 location <- unUrl <$> arbitrary 73 location <- unUrl <$> arbitrary
57 ident <- unIdent <$> arbitrary 74 ident <- unIdent <$> arbitrary
58 return $ SimpleMac secret (create secret ident location) 75 fpcavs <- listOf arbitrary
76 let mac = foldl (flip addFirstPartyCaveat) (create secret ident location) (map unEqlike fpcavs)
77 return $ SimpleMac secret mac
59 78
60 79
diff --git a/test/Crypto/Macaroon/Serializer/Base64/Tests.hs b/test/Crypto/Macaroon/Serializer/Base64/Tests.hs
index 19084af..ea3bed9 100644
--- a/test/Crypto/Macaroon/Serializer/Base64/Tests.hs
+++ b/test/Crypto/Macaroon/Serializer/Base64/Tests.hs
@@ -30,7 +30,7 @@ tests = testGroup "Crypto.Macaroon.Serializer.Base64" [ basic
30 ] 30 ]
31 31
32basicQC = testProperty "Reversibility" $ 32basicQC = testProperty "Reversibility" $
33 forAll (macaroon <$> arbitrary) (\m -> deserialize (serialize m) == Right m) 33 \sm -> deserialize (serialize (macaroon sm)) == Right (macaroon sm)
34 34
35m :: Macaroon 35m :: Macaroon
36m = create secret key loc 36m = create secret key loc
diff --git a/test/Crypto/Macaroon/Verifier/Tests.hs b/test/Crypto/Macaroon/Verifier/Tests.hs
index 92a8a21..101fa26 100644
--- a/test/Crypto/Macaroon/Verifier/Tests.hs
+++ b/test/Crypto/Macaroon/Verifier/Tests.hs
@@ -12,9 +12,11 @@ This test suite is based on the pymacaroons test suite:
12module Crypto.Macaroon.Verifier.Tests where 12module Crypto.Macaroon.Verifier.Tests where
13 13
14 14
15import Data.List
15import qualified Data.ByteString.Char8 as B8 16import qualified Data.ByteString.Char8 as B8
16import Test.Tasty 17import Test.Tasty
17import Test.Tasty.HUnit 18-- import Test.Tasty.HUnit
19import Test.Tasty.QuickCheck
18 20
19import Crypto.Macaroon 21import Crypto.Macaroon
20import Crypto.Macaroon.Verifier 22import Crypto.Macaroon.Verifier
@@ -23,8 +25,12 @@ import Crypto.Macaroon.Instances
23 25
24tests :: TestTree 26tests :: TestTree
25tests = testGroup "Crypto.Macaroon.Verifier" [ sigs 27tests = testGroup "Crypto.Macaroon.Verifier" [ sigs
28 , firstParty
26 ] 29 ]
27 30
31{-
32 - Test fixtures
33 -}
28sec = B8.pack "this is our super secret key; only we should know it" 34sec = B8.pack "this is our super secret key; only we should know it"
29 35
30m :: Macaroon 36m :: Macaroon
@@ -37,23 +43,54 @@ m2 :: Macaroon
37m2 = addFirstPartyCaveat "test = caveat" m 43m2 = addFirstPartyCaveat "test = caveat" m
38 44
39m3 :: Macaroon 45m3 :: Macaroon
40m3 = addFirstPartyCaveat "test = acaveat" m 46m3 = addFirstPartyCaveat "value = 42" m2
41 47
42sigs = testGroup "Signatures" [ basic 48exTC = verifyExact "test" "caveat" (many' letter_ascii) <???> "test = caveat"
43 , minted 49exTZ = verifyExact "test" "bleh" (many' letter_ascii) <???> "test = bleh"
44 ] 50exV42 = verifyExact "value" 42 decimal <???> "value = 42"
45 51exV43 = verifyExact "value" 43 decimal <???> "value = 43"
46basic = testCase "Basic Macaroon Signature" $ 52
47 Success @=? verifySig sec m 53funTCPre = verifyFun "test" ("cav" `isPrefixOf`) (many' letter_ascii) <???> "test startsWith cav"
48 54funTV43lte = verifyFun "value" (<= 43) decimal <???> "value <= 43"
49 55
50minted :: TestTree 56allvs = [exTC, exTZ, exV42, exV43, funTCPre, funTV43lte]
51minted = testGroup "Macaroon with first party caveats" [ one 57
52 , two 58{-
53 ] 59 - Tests
54one = testCase "One caveat" $ 60 -}
55 Success @=? verifySig sec m2 61sigs = testProperty "Signatures" $ \sm -> verifySig (secret sm) (macaroon sm) == Ok
56 62
57two = testCase "Two caveats" $ 63firstParty = testGroup "First party caveats" [
58 Success @=? verifySig sec m3 64 testGroup "Pure verifiers" [
59 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 ]