aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--README.md32
-rw-r--r--src/Crypto/Macaroon.hs31
-rw-r--r--src/Crypto/Macaroon/Binder.hs2
-rw-r--r--src/Crypto/Macaroon/Internal.hs33
-rw-r--r--test/Crypto/Macaroon/Tests.hs48
5 files changed, 90 insertions, 56 deletions
diff --git a/README.md b/README.md
index f3fd91a..45b84e0 100644
--- a/README.md
+++ b/README.md
@@ -5,14 +5,34 @@ Macaroons is a pure haskell implementation of macaroons. It aims to provide
5compatibility at a serialized level with the [reference implementation](https://github.com/rescrv/libmacaroons) 5compatibility at a serialized level with the [reference implementation](https://github.com/rescrv/libmacaroons)
6and the [python implementation](https://github.com/ecordell/pymacaroons) 6and the [python implementation](https://github.com/ecordell/pymacaroons)
7 7
8[Google paper on macaroons](http://research.google.com/pubs/pub41892.html) 8**WARNING: This library has not been audited by security experts.**
9[Macaroons at Mozilla](https://air.mozilla.org/macaroons-cookies-with-contextual-caveats-for-decentralized-authorization-in-the-cloud/) 9**There is no error handling at the moment, everyhting is silently accepted**
10[Time for better security in NoSQL](http://hackingdistributed.com/2014/11/23/macaroons-in-hyperdex/) 10
11[Pure java implementation](https://github.com/nitram509/jmacaroons) 11It is developed in the purpose of exploration purposes, and would need much
12more attention if it were to be used in production.
13
14References
15==========
16
17Papers and articles
18-------------------
19
20- [Google paper on macaroons](http://research.google.com/pubs/pub41892.html)
21- [Macaroons at Mozilla](https://air.mozilla.org/macaroons-cookies-with-contextual-caveats-for-decentralized-authorization-in-the-cloud/)
22- [Time for better security in NoSQL](http://hackingdistributed.com/2014/11/23/macaroons-in-hyperdex/)
23
24Implementations
25---------------
26
27- [C](https://github.com/rescrv/libmacaroons)
28- [Java](https://github.com/nitram509/jmacaroons)
29- [Python](https://github.com/ecordell/pymacaroons)
12 30
13TODO 31TODO
14==== 32====
15 33
16- Verifiy Macaroons 34- Verify Macaroons
17- Discharge Macaroons 35- Discharge Macaroons
18 36- JSON serialization
37- Quickcheck tests
38- Error handling
diff --git a/src/Crypto/Macaroon.hs b/src/Crypto/Macaroon.hs
index 819a9eb..42e4a07 100644
--- a/src/Crypto/Macaroon.hs
+++ b/src/Crypto/Macaroon.hs
@@ -8,18 +8,16 @@ Maintainer : julien.tanguy@jhome.fr
8Stability : experimental 8Stability : experimental
9Portability : portable 9Portability : portable
10 10
11
12Pure haskell implementations of macaroons. 11Pure haskell implementations of macaroons.
13 12
14Warning: this implementation has not been audited by security experts. 13Warning: this implementation has not been audited by security experts.
15Use it with caution. 14Do not use in production
16 15
17 16
18References: 17References:
19 18
20- Macaroons: Cookies with Contextual Caveats for Decentralized Authorization in the Cloud <http://research.google.com/pubs/pub41892.html> 19- Macaroons: Cookies with Contextual Caveats for Decentralized Authorization in the Cloud <http://research.google.com/pubs/pub41892.html>
21- Time for better security in NoSQL <http://hackingdistributed.com/2014/11/23/macaroons-in-hyperdex> 20- Time for better security in NoSQL <http://hackingdistributed.com/2014/11/23/macaroons-in-hyperdex>
22
23-} 21-}
24module Crypto.Macaroon ( 22module Crypto.Macaroon (
25 -- * Types 23 -- * Types
@@ -50,11 +48,14 @@ module Crypto.Macaroon (
50 48
51import Crypto.Cipher.AES 49import Crypto.Cipher.AES
52import Crypto.Hash 50import Crypto.Hash
51import Data.Char
53import Data.Byteable 52import Data.Byteable
54import qualified Data.ByteString as BS 53import qualified Data.ByteString as BS
55import qualified Data.ByteString.Base64.URL as B64 54import qualified Data.ByteString.Base64.URL as B64
56import qualified Data.ByteString.Char8 as B8 55import qualified Data.ByteString.Char8 as B8
57import Data.Hex 56import Data.Hex
57import Data.Word
58import Data.Serialize
58 59
59import Crypto.Macaroon.Internal 60import Crypto.Macaroon.Internal
60 61
@@ -62,7 +63,7 @@ import Crypto.Macaroon.Internal
62create :: Key -> Key -> Location -> Macaroon 63create :: Key -> Key -> Location -> Macaroon
63create secret ident loc = MkMacaroon loc ident [] (toBytes (hmac derivedKey ident :: HMAC SHA256)) 64create secret ident loc = MkMacaroon loc ident [] (toBytes (hmac derivedKey ident :: HMAC SHA256))
64 where 65 where
65 derivedKey = toBytes $ (hmac "macaroons-key-generator" secret :: HMAC SHA256) 66 derivedKey = toBytes (hmac "macaroons-key-generator" secret :: HMAC SHA256)
66 67
67caveatLoc :: Caveat -> Location 68caveatLoc :: Caveat -> Location
68caveatLoc = cl 69caveatLoc = cl
@@ -74,17 +75,7 @@ caveatVId :: Caveat -> Key
74caveatVId = vid 75caveatVId = vid
75 76
76inspect :: Macaroon -> String 77inspect :: Macaroon -> String
77inspect m = unlines [ "location " ++ show (location m) 78inspect = show
78 , "identifier " ++ show (identifier m)
79 , (concatMap (showCav (location m)) (caveats m))
80 , "signature " ++ show (hex $ signature m)
81 ]
82 where
83 showCav loc c | cl c == loc && vid c == BS.empty = "cid " ++ show (cid c)
84 | otherwise = unlines [ "cid " ++ show (cid c)
85 , "vid " ++ show (vid c)
86 , "cl " ++ show (cl c)
87 ]
88 79
89serialize :: Macaroon -> BS.ByteString 80serialize :: Macaroon -> BS.ByteString
90serialize m = B8.filter (/= '=') . B64.encode $ packets 81serialize m = B8.filter (/= '=') . B64.encode $ packets
@@ -100,6 +91,16 @@ serialize m = B8.filter (/= '=') . B64.encode $ packets
100 , putPacket "vid" (vid c) 91 , putPacket "vid" (vid c)
101 , putPacket "cl" (cl c) 92 , putPacket "cl" (cl c)
102 ] 93 ]
94 putPacket key dat = BS.concat [
95 B8.map toLower . hex . encode $ (fromIntegral size :: Word16)
96 , key
97 , " "
98 , dat
99 , "\n"
100 ]
101 where
102 size = 4 + 2 + BS.length key + BS.length dat
103
103 104
104 105
105 106
diff --git a/src/Crypto/Macaroon/Binder.hs b/src/Crypto/Macaroon/Binder.hs
index 3ec3d67..91f07ce 100644
--- a/src/Crypto/Macaroon/Binder.hs
+++ b/src/Crypto/Macaroon/Binder.hs
@@ -24,5 +24,5 @@ newtype Binder = Binder { bind :: Macaroon -> Macaroon -> BS.ByteString }
24 24
25-- | Binder which concatenates the two signatures and hashes them 25-- | Binder which concatenates the two signatures and hashes them
26hashSigs :: Binder 26hashSigs :: Binder
27hashSigs = Binder $ \m m' -> toBytes $ (HMAC . hash $ BS.append (toBytes $ signature m') (toBytes $ signature m) :: HMAC SHA256) 27hashSigs = Binder $ \m m' -> toBytes (HMAC . hash $ BS.append (toBytes $ signature m') (toBytes $ signature m) :: HMAC SHA256)
28 28
diff --git a/src/Crypto/Macaroon/Internal.hs b/src/Crypto/Macaroon/Internal.hs
index fc50486..82ce0b4 100644
--- a/src/Crypto/Macaroon/Internal.hs
+++ b/src/Crypto/Macaroon/Internal.hs
@@ -21,10 +21,8 @@ import Data.Byteable
21import qualified Data.ByteString as BS 21import qualified Data.ByteString as BS
22import qualified Data.ByteString.Base64 as B64 22import qualified Data.ByteString.Base64 as B64
23import qualified Data.ByteString.Char8 as B8 23import qualified Data.ByteString.Char8 as B8
24import Data.Char
25import Data.Hex 24import Data.Hex
26import Data.Serialize 25import Data.List
27import Data.Word
28 26
29-- |Type alias for Macaroons and Caveat keys and identifiers 27-- |Type alias for Macaroons and Caveat keys and identifiers
30type Key = BS.ByteString 28type Key = BS.ByteString
@@ -45,6 +43,14 @@ data Macaroon = MkMacaroon { location :: Location
45 -- ^ Macaroon HMAC signature 43 -- ^ Macaroon HMAC signature
46 } deriving (Eq) 44 } deriving (Eq)
47 45
46instance Show Macaroon where
47 -- We use intercalate because unlines would add a trailing newline
48 show (MkMacaroon l i c s) = intercalate "\n" [
49 "location " ++ B8.unpack l
50 , "identifier " ++ B8.unpack i
51 , concatMap show c
52 , "signature " ++ B8.unpack (hex s)
53 ]
48 54
49instance NFData Macaroon where 55instance NFData Macaroon where
50 rnf (MkMacaroon loc ident cavs sig) = rnf loc `seq` rnf ident `seq` rnf cavs `seq` rnf sig 56 rnf (MkMacaroon loc ident cavs sig) = rnf loc `seq` rnf ident `seq` rnf cavs `seq` rnf sig
@@ -60,21 +66,18 @@ data Caveat = MkCaveat { cid :: Key
60 66
61 } deriving (Eq) 67 } deriving (Eq)
62 68
69instance Show Caveat where
70 show (MkCaveat c v l) | v == BS.empty = "cid " ++ B8.unpack c
71 | otherwise = unlines [ "cid " ++ B8.unpack c
72 , "vid " ++ B8.unpack v
73 , "cl " ++ B8.unpack l
74 ]
75
76
63instance NFData Caveat where 77instance NFData Caveat where
64 rnf (MkCaveat cid vid cl) = rnf cid `seq` rnf vid `seq` rnf cl 78 rnf (MkCaveat cid vid cl) = rnf cid `seq` rnf vid `seq` rnf cl
65 79
66 80
67putPacket :: BS.ByteString -> BS.ByteString -> BS.ByteString
68putPacket key dat = BS.concat [
69 B8.map toLower . hex . encode $ (fromIntegral size :: Word16)
70 , key
71 , " "
72 , dat
73 , "\n"
74 ]
75 where
76 size = 4 + 2 + BS.length key + BS.length dat
77
78addCaveat :: Location 81addCaveat :: Location
79 -> Key 82 -> Key
80 -> Key 83 -> Key
@@ -84,5 +87,5 @@ addCaveat loc cid vid m = m { caveats = cavs ++ [cav'], signature = sig}
84 where 87 where
85 cavs = caveats m 88 cavs = caveats m
86 cav' = MkCaveat cid vid loc 89 cav' = MkCaveat cid vid loc
87 sig = toBytes $ (hmac (signature m) (BS.append vid cid) :: HMAC SHA256) 90 sig = toBytes (hmac (signature m) (BS.append vid cid) :: HMAC SHA256)
88 91
diff --git a/test/Crypto/Macaroon/Tests.hs b/test/Crypto/Macaroon/Tests.hs
index cdfb620..f57bec3 100644
--- a/test/Crypto/Macaroon/Tests.hs
+++ b/test/Crypto/Macaroon/Tests.hs
@@ -23,6 +23,7 @@ tests :: TestTree
23tests = testGroup "Crypto.Macaroon" [ basicSignature 23tests = testGroup "Crypto.Macaroon" [ basicSignature
24 , basicSerialize 24 , basicSerialize
25 , basicMint 25 , basicMint
26 , basicInspect
26 , basicMintTrimmed 27 , basicMintTrimmed
27 ] 28 ]
28 29
@@ -34,12 +35,39 @@ m = create secret key loc
34 key = B8.pack "we used our secret key" 35 key = B8.pack "we used our secret key"
35 loc = B8.pack "http://mybank/" 36 loc = B8.pack "http://mybank/"
36 37
38basicSignature = testCase "Basic signature" $
39 "E3D9E02908526C4C0039AE15114115D97FDD68BF2BA379B342AAF0F617D0552F" @=? (hex . signature) m
40
41basicSerialize = testCase "Serialization" $
42 "MDAxY2xvY2F0aW9uIGh0dHA6Ly9teWJhbmsvCjAwMjZpZGVudG\
43 \lmaWVyIHdlIHVzZWQgb3VyIHNlY3JldCBrZXkKMDAyZnNpZ25h\
44 \dHVyZSDj2eApCFJsTAA5rhURQRXZf91ovyujebNCqvD2F9BVLwo" @=? serialize m
45
37m2 :: Macaroon 46m2 :: Macaroon
38m2 = addFirstPartyCaveat "test = caveat" m 47m2 = addFirstPartyCaveat "test = caveat" m
39 48
49basicInspect = testCase "Inspect" $
50 "location http://mybank/\nidentifier we used\
51 \ our secret key\ncid test = caveat\nsignature\
52 \ 197BAC7A044AF33332865B9266E26D49\
53 \3BDD668A660E44D88CE1A998C23DBD67" @=? inspect m2
54
55
56basicMint = testCase "First Party Caveat" $
57 "MDAxY2xvY2F0aW9uIGh0dHA6Ly9teWJhbmsvCjAwMjZpZGVudGlmaWVyIHdlIHVzZ\
58 \WQgb3VyIHNlY3JldCBrZXkKMDAxNmNpZCB0ZXN0ID0gY2F2ZWF0CjAwMmZzaWduYXR1cmUgGXusegR\
59 \K8zMyhluSZuJtSTvdZopmDkTYjOGpmMI9vWcK" @=? serialize m2
60
61
40m3 :: Macaroon 62m3 :: Macaroon
41m3 = addFirstPartyCaveat "test = acaveat" m 63m3 = addFirstPartyCaveat "test = acaveat" m
42 64
65basicMintTrimmed = testCase "Trimmed base64" $
66 "MDAxY2xvY2F0aW9uIGh0dHA6Ly9teWJhbmsvCjAwMjZpZGVudGlmaWVyIHdlIHVz\
67 \ZWQgb3VyIHNlY3JldCBrZXkKMDAxN2NpZCB0ZXN0ID0gYWNhdmVhdAowMDJmc2ln\
68 \bmF0dXJlIJRJ_V3WNJQnqlVq5eez7spnltwU_AXs8NIRY739sHooCg" @=? serialize m3
69
70
43m4 :: Macaroon 71m4 :: Macaroon
44m4 = addThirdPartyCaveat caveat_key caveat_id caveat_loc n 72m4 = addThirdPartyCaveat caveat_key caveat_id caveat_loc n
45 where 73 where
@@ -52,23 +80,5 @@ m4 = addThirdPartyCaveat caveat_key caveat_id caveat_loc n
52 caveat_loc = B8.pack "http://auth.mybank/" 80 caveat_loc = B8.pack "http://auth.mybank/"
53 81
54 82
55basicSignature = testCase "Basic signature" $ 83basicThirdParty = testCase "Third Party Caveat" $
56 "E3D9E02908526C4C0039AE15114115D97FDD68BF2BA379B342AAF0F617D0552F" @=? (hex . signature) m
57
58basicSerialize = testCase "Serialization" $
59 "MDAxY2xvY2F0aW9uIGh0dHA6Ly9teWJhbmsvCjAwMjZpZGVudG\
60 \lmaWVyIHdlIHVzZWQgb3VyIHNlY3JldCBrZXkKMDAyZnNpZ25h\
61 \dHVyZSDj2eApCFJsTAA5rhURQRXZf91ovyujebNCqvD2F9BVLwo" @=? serialize m
62
63basicMint = testCase "First Party Caveat" $
64 "MDAxY2xvY2F0aW9uIGh0dHA6Ly9teWJhbmsvCjAwMjZpZGVudGlmaWVyIHdlIHVzZ\
65 \WQgb3VyIHNlY3JldCBrZXkKMDAxNmNpZCB0ZXN0ID0gY2F2ZWF0CjAwMmZzaWduYXR1cmUgGXusegR\
66 \K8zMyhluSZuJtSTvdZopmDkTYjOGpmMI9vWcK" @=? serialize m2
67
68basicMintTrimmed = testCase "Trimmed base64" $
69 "MDAxY2xvY2F0aW9uIGh0dHA6Ly9teWJhbmsvCjAwMjZpZGVudGlmaWVyIHdlIHVz\
70 \ZWQgb3VyIHNlY3JldCBrZXkKMDAxN2NpZCB0ZXN0ID0gYWNhdmVhdAowMDJmc2ln\
71 \bmF0dXJlIJRJ_V3WNJQnqlVq5eez7spnltwU_AXs8NIRY739sHooCg" @=? serialize m3
72
73basicThirdParty = testCase "Third Party Caveat" $
74 "6B99EDB2EC6D7A4382071D7D41A0BF7DFA27D87D2F9FEA86E330D7850FFDA2B2" @=? (hex . signature) m4 84 "6B99EDB2EC6D7A4382071D7D41A0BF7DFA27D87D2F9FEA86E330D7850FFDA2B2" @=? (hex . signature) m4