Skip to content

Commit

Permalink
Store discharge macaroons on the verifier, and don't allow them to be
Browse files Browse the repository at this point in the history
used more than once. This prevents an infinite recursion on a discharge
that requires itself.

This is a breaking change to the python verifier API, but not to the
macaroon protocol API.

fixes #40
  • Loading branch information
ecordell committed Jan 8, 2019
1 parent 06b5511 commit 21efc26
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 30 deletions.
1 change: 0 additions & 1 deletion pymacaroons/caveat_delegates/base_third_party.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ def verify_third_party_caveat(self,
caveat,
root,
macaroon,
discharge_macaroons,
signature):
pass

Expand Down
24 changes: 14 additions & 10 deletions pymacaroons/caveat_delegates/third_party.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,26 +54,33 @@ def add_third_party_caveat(self,

class ThirdPartyCaveatVerifierDelegate(BaseThirdPartyCaveatVerifierDelegate):

def __init__(self, *args, **kwargs):
def __init__(self, discharge_macaroons=None, *args, **kwargs):
super(ThirdPartyCaveatVerifierDelegate, self).__init__(*args, **kwargs)
if discharge_macaroons:
self.discharge_macaroons = {m.identifier_bytes: m for m in discharge_macaroons}

def verify_third_party_caveat(self,
verifier,
caveat,
root,
macaroon,
discharge_macaroons,
signature):
caveat_macaroon = self._caveat_macaroon(caveat, discharge_macaroons)
caveat_macaroon = self._caveat_macaroon(caveat)
caveat_key = self._extract_caveat_key(signature, caveat)

discharge = self.discharge_macaroons[caveat.caveat_id_bytes]
del self.discharge_macaroons[caveat.caveat_id_bytes]

caveat_met = verifier.verify_discharge(
root,
caveat_macaroon,
caveat_key,
discharge_macaroons=discharge_macaroons
)


# if the caveat wasn't successfully discharged, restore the discharge macaroon to the available set
if not caveat_met:
self.discharge_macaroons[caveat.caveat_id_bytes] = discharge

return caveat_met

def update_signature(self, signature, caveat):
Expand All @@ -85,11 +92,8 @@ def update_signature(self, signature, caveat):
)
)

def _caveat_macaroon(self, caveat, discharge_macaroons):
# TODO: index discharge macaroons by identifier
caveat_macaroon = next(
(m for m in discharge_macaroons
if m.identifier_bytes == caveat.caveat_id_bytes), None)
def _caveat_macaroon(self, caveat):
caveat_macaroon = self.discharge_macaroons.get(caveat.caveat_id_bytes, None)

if not caveat_macaroon:
raise MacaroonUnmetCaveatException(
Expand Down
22 changes: 9 additions & 13 deletions pymacaroons/verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@

class Verifier(object):

def __init__(self):
def __init__(self, discharge_macaroons=None):
self.predicates = []
self.callbacks = [self.verify_exact]
self.calculated_signature = None
self.first_party_caveat_verifier_delegate = (
FirstPartyCaveatVerifierDelegate()
)
self.third_party_caveat_verifier_delegate = (
ThirdPartyCaveatVerifierDelegate()
ThirdPartyCaveatVerifierDelegate(discharge_macaroons=discharge_macaroons)
)

def satisfy_exact(self, predicate):
Expand All @@ -46,22 +46,21 @@ def satisfy_general(self, func):
def verify_exact(self, predicate):
return predicate in self.predicates

def verify(self, macaroon, key, discharge_macaroons=None):
def verify(self, macaroon, key):
key = generate_derived_key(convert_to_bytes(key))
return self.verify_discharge(
macaroon,
macaroon,
key,
discharge_macaroons
key,
)

def verify_discharge(self, root, discharge, key, discharge_macaroons=None):
def verify_discharge(self, root, discharge, key):
calculated_signature = hmac_digest(
key, discharge.identifier_bytes
)

calculated_signature = self._verify_caveats(
root, discharge, discharge_macaroons, calculated_signature
root, discharge, calculated_signature
)

if root != discharge:
Expand All @@ -78,18 +77,16 @@ def verify_discharge(self, root, discharge, key, discharge_macaroons=None):

return True

def _verify_caveats(self, root, macaroon, discharge_macaroons, signature):
def _verify_caveats(self, root, macaroon, signature):
for caveat in macaroon.caveats:
if self._caveat_met(root,
caveat,
macaroon,
discharge_macaroons,
signature):
signature = self._update_signature(caveat, signature)
return signature

def _caveat_met(self, root, caveat, macaroon,
discharge_macaroons, signature):
def _caveat_met(self, root, caveat, macaroon, signature):
if caveat.first_party():
return (
self
Expand All @@ -101,8 +98,7 @@ def _caveat_met(self, root, caveat, macaroon,
self
.third_party_caveat_verifier_delegate
.verify_third_party_caveat(
self, caveat, root, macaroon,
discharge_macaroons, signature,
self, caveat, root, macaroon, signature,
)
)

Expand Down
16 changes: 12 additions & 4 deletions tests/functional_tests/functional_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,14 +398,13 @@ def test_verify_third_party_caveats(self):
discharge.add_first_party_caveat('time < 2015-01-01T00:00')
protected = m.prepare_for_request(discharge)

v = Verifier()
v = Verifier(discharge_macaroons=[protected])
v.satisfy_exact('account = 3735928559')
v.satisfy_exact('time < 2015-01-01T00:00')
verified = v.verify(
m,
'this is a different super-secret key; \
never use the same secret twice',
discharge_macaroons=[protected]
never use the same secret twice'
)
assert_true(verified)

Expand All @@ -425,7 +424,7 @@ def test_verify_third_party_caveats_multi_level(self):
discharge1 = root.prepare_for_request(discharge1)
discharge2 = root.prepare_for_request(discharge2)

verified = Verifier().verify(root, "root-key", [discharge1, discharge2])
verified = Verifier(discharge_macaroons=[discharge1, discharge2]).verify(root, "root-key")
assert_true(verified)

@patch('nacl.secret.random')
Expand All @@ -452,3 +451,12 @@ def test_inspect(self, rand_nonce):
'vid AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA68NYajhiFuHnKGSNcVhkAwgbs0VZ0yK2o+q0Aq9+bONkXw7ky7HAuhCLO9hhaMMc\n'
'cl http://auth.mybank/\n'
'signature 7a9289bfbb92d725f748bbcb4f3e04e56b7021513ebeed8411bfba10a16a662e'))

@raises(MacaroonUnmetCaveatException)
def test_mutual_discharge(self):
m1 = Macaroon(location="", identifier="root-id", key="root-key")
m1.add_third_party_caveat("bob", "bob-caveat-root-key", "bob-is-great")
m2 = Macaroon(location="bob", identifier="bob-is-great", key="bob-caveat-root-key")
m2.add_third_party_caveat("charlie", "bob-caveat-root-key", "bob-is-great")
m2 = m1.prepare_for_request(m2)
Verifier(discharge_macaroons=[m2]).verify(m1, "root-key")
3 changes: 1 addition & 2 deletions tests/functional_tests/serialization_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,10 @@ def assert_macaroon(m, discharge, version):
assert_equal(m.location, 'my location')
assert_equal(m.version, version)
assert_equal(m.identifier_bytes, b'my identifier')
v = Verifier()
v = Verifier(discharge_macaroons=[discharge])
v.satisfy_exact('fp caveat')
verified = v.verify(
m,
"my secret key",
discharge_macaroons=[discharge],
)
assert_true(verified)

0 comments on commit 21efc26

Please sign in to comment.