diff --git a/docs/source/configuration.rst b/docs/source/configuration.rst index 5f27eb1a7b5915dbe01785bf60f2fc8360655f91..dce8feab367d241aa4991f2c87769fcd6bf42562 100644 --- a/docs/source/configuration.rst +++ b/docs/source/configuration.rst @@ -22,8 +22,11 @@ For example:: [storageclient.plugins.privatestorageio-zkapauthz-v1] redeemer = dummy + issuer-public-key = YXNkYXNkYXNkYXNkYXNkCg== -A value of ``ristretto`` causes the client to speak Ristretto-flavored PrivacyPass to an issuer server. +The value of the ``issuer-public-key`` item is included as-is as the public key in the successful redemption response. + +A ``redeemer`` value of ``ristretto`` causes the client to speak Ristretto-flavored PrivacyPass to an issuer server. In this case, the ``ristretto-issuer-root-url`` item is also required. For example:: diff --git a/src/_zkapauthorizer/controller.py b/src/_zkapauthorizer/controller.py index 15d8395080c10777a57cd227fa05e70ffdc301a5..69571ca2c03ab26d3389775df4849f1134b0e256 100644 --- a/src/_zkapauthorizer/controller.py +++ b/src/_zkapauthorizer/controller.py @@ -372,12 +372,25 @@ class DummyRedeemer(object): A ``DummyRedeemer`` pretends to redeem vouchers for ZKAPs. Instead of really redeeming them, it makes up some fake ZKAPs and pretends those are the result. + + :ivar unicode _public_key: The base64-encoded public key to return with + all successful redemption results. As with the tokens returned by + this redeemer, chances are this is not actually a valid public key. + Its corresponding private key certainly has not been used to sign + anything. """ - _public_key = attr.ib(default=None) + _public_key = attr.ib( + validator=attr.validators.instance_of(unicode), + ) @classmethod def make(cls, section_name, node_config, announcement, reactor): - return cls() + return cls( + node_config.get_config( + section=section_name, + option=u"issuer-public-key", + ).decode(u"utf-8"), + ) def random_tokens_for_voucher(self, voucher, counter, count): """ diff --git a/src/_zkapauthorizer/tests/strategies.py b/src/_zkapauthorizer/tests/strategies.py index 77c854a2bff7109130f8b41257fbca9671ce3262..c9f46fd644aeef83637453baeb643e5c8001cfc7 100644 --- a/src/_zkapauthorizer/tests/strategies.py +++ b/src/_zkapauthorizer/tests/strategies.py @@ -245,10 +245,12 @@ def client_dummyredeemer_configurations(): """ Build DummyRedeemer-using configuration values for the client-side plugin. """ - return just({ - u"redeemer": u"dummy", - u"default-token-count": u"32", - }) + return dummy_ristretto_keys().map( + lambda key: { + u"redeemer": u"dummy", + u"issuer-public-key": key, + u"default-token-count": u"32", + }) def token_counts(): diff --git a/src/_zkapauthorizer/tests/test_client_resource.py b/src/_zkapauthorizer/tests/test_client_resource.py index 5c18d34e83c6b8884e827febc7713e261b4d0103..79c1cdcd2eec1c0a2905183be9a67631727582f8 100644 --- a/src/_zkapauthorizer/tests/test_client_resource.py +++ b/src/_zkapauthorizer/tests/test_client_resource.py @@ -192,6 +192,32 @@ from .json import ( TRANSIENT_ERROR = u"something went wrong, who knows what" +def get_dummyredeemer_public_key(plugin_name, node_config): + """ + Get the issuer public key a ``DummyRedeemer`` has been configured with. + + :param unicode plugin_name: The plugin name to use to choose a + configuration section. + + :param _Config node_config: See ``from_configuration``. + """ + section_name = u"storageclient.plugins.{}".format(plugin_name) + redeemer_kind = node_config.get_config( + section=section_name, + option=u"redeemer", + ) + if redeemer_kind != "dummy": + raise ValueError( + "Cannot read dummy redeemer public key from configuration for {!r} redeemer.".format( + redeemer_kind, + ), + ) + return node_config.get_config( + section=section_name, + option=u"issuer-public-key", + ).decode("utf-8") + + # Helper to work-around https://github.com/twisted/treq/issues/161 def uncooperator(started=True): return Cooperator( @@ -1108,6 +1134,7 @@ class VoucherTests(TestCase): are included in a json-encoded response body. """ count = get_token_count("privatestorageio-zkapauthz-v1", config) + public_key = get_dummyredeemer_public_key("privatestorageio-zkapauthz-v1", config) return self._test_get_known_voucher( config, api_auth_token, @@ -1120,7 +1147,7 @@ class VoucherTests(TestCase): state=Equals(Redeemed( finished=now, token_count=count, - public_key=None, + public_key=public_key, )), ), ) @@ -1286,6 +1313,7 @@ class VoucherTests(TestCase): vouchers. """ count = get_token_count("privatestorageio-zkapauthz-v1", config) + public_key = get_dummyredeemer_public_key("privatestorageio-zkapauthz-v1", config) return self._test_list_vouchers( config, api_auth_token, @@ -1300,7 +1328,7 @@ class VoucherTests(TestCase): state=Redeemed( finished=now, token_count=count, - public_key=None, + public_key=public_key, ), ).marshal() for voucher diff --git a/src/_zkapauthorizer/tests/test_controller.py b/src/_zkapauthorizer/tests/test_controller.py index 25568d09803c21def1bf10a49d7b088e6bf00895..73398aabd9fcbdaecdb4e8b4ed4c0dcbb27b2ff8 100644 --- a/src/_zkapauthorizer/tests/test_controller.py +++ b/src/_zkapauthorizer/tests/test_controller.py @@ -220,8 +220,8 @@ class PaymentControllerTests(TestCase): """ Tests for ``PaymentController``. """ - @given(tahoe_configs(), datetimes(), vouchers()) - def test_should_not_redeem(self, get_config, now, voucher): + @given(tahoe_configs(), datetimes(), vouchers(), dummy_ristretto_keys()) + def test_should_not_redeem(self, get_config, now, voucher, public_key): """ ``PaymentController.redeem`` raises ``ValueError`` if passed a voucher in a state when redemption should not be started. @@ -229,7 +229,7 @@ class PaymentControllerTests(TestCase): store = self.useFixture(TemporaryVoucherStore(get_config, lambda: now)).store controller = PaymentController( store, - DummyRedeemer(), + DummyRedeemer(public_key), default_token_count=100, clock=Clock(), ) @@ -280,8 +280,8 @@ class PaymentControllerTests(TestCase): Equals(model_Pending(counter=0)), ) - @given(tahoe_configs(), datetimes(), vouchers(), voucher_counters()) - def test_redeeming(self, get_config, now, voucher, num_successes): + @given(tahoe_configs(), datetimes(), vouchers(), voucher_counters(), dummy_ristretto_keys()) + def test_redeeming(self, get_config, now, voucher, num_successes, public_key): """ A ``Voucher`` is marked redeeming while ``IRedeemer.redeem`` is actively working on redeeming it with a counter value that reflects the number @@ -292,7 +292,7 @@ class PaymentControllerTests(TestCase): # that. counter = num_successes + 1 redeemer = IndexedRedeemer( - [DummyRedeemer()] * num_successes + + [DummyRedeemer(public_key)] * num_successes + [NonRedeemer()], ) store = self.useFixture(TemporaryVoucherStore(get_config, lambda: now)).store @@ -327,8 +327,9 @@ class PaymentControllerTests(TestCase): vouchers(), voucher_counters(), voucher_counters().map(lambda v: v + 1), + dummy_ristretto_keys(), ) - def test_restart_redeeming(self, get_config, now, voucher, before_restart, after_restart): + def test_restart_redeeming(self, get_config, now, voucher, before_restart, after_restart, public_key): """ If some redemption groups for a voucher have succeeded but the process is interrupted, redemption begins at the first incomplete redemption @@ -354,7 +355,7 @@ class PaymentControllerTests(TestCase): store, # It will let `before_restart` attempts succeed before hanging. IndexedRedeemer( - [DummyRedeemer()] * before_restart + + [DummyRedeemer(public_key)] * before_restart + [NonRedeemer()] * after_restart, ), default_token_count=num_tokens, @@ -375,7 +376,7 @@ class PaymentControllerTests(TestCase): # not succeed or did not get started on the first try. IndexedRedeemer( [NonRedeemer()] * before_restart + - [DummyRedeemer()] * after_restart, + [DummyRedeemer(public_key)] * after_restart, ), # The default token count for this new controller doesn't # matter. The redemption attempt already started with some @@ -398,7 +399,7 @@ class PaymentControllerTests(TestCase): model_Redeemed( finished=now, token_count=num_tokens, - public_key=None, + public_key=public_key, ), ), ) @@ -489,8 +490,8 @@ class PaymentControllerTests(TestCase): ), ) - @given(tahoe_configs(), datetimes(), vouchers()) - def test_redeem_pending_on_startup(self, get_config, now, voucher): + @given(tahoe_configs(), datetimes(), vouchers(), dummy_ristretto_keys()) + def test_redeem_pending_on_startup(self, get_config, now, voucher, public_key): """ When ``PaymentController`` is created, any vouchers in the store in the pending state are redeemed. @@ -520,7 +521,7 @@ class PaymentControllerTests(TestCase): # `__init__` side-effect. :/ success_controller = PaymentController( store, - DummyRedeemer(), + DummyRedeemer(public_key), default_token_count=100, clock=Clock(), ) diff --git a/src/_zkapauthorizer/tests/test_plugin.py b/src/_zkapauthorizer/tests/test_plugin.py index 1d460324c840460b7c5162bdea0ae677adb96d39..0db1a375d2ffe5c1c7143920909f4a346f6791c5 100644 --- a/src/_zkapauthorizer/tests/test_plugin.py +++ b/src/_zkapauthorizer/tests/test_plugin.py @@ -156,6 +156,7 @@ from .strategies import ( sizes, pass_counts, ristretto_signing_keys, + dummy_ristretto_keys, ) from .matchers import ( Provides, @@ -469,6 +470,7 @@ class ClientPluginTests(TestCase): announcement=announcements(), voucher=vouchers(), num_passes=pass_counts(), + public_key=dummy_ristretto_keys(), ) @capture_logging(lambda self, logger: logger.validate()) def test_unblinded_tokens_spent( @@ -479,6 +481,7 @@ class ClientPluginTests(TestCase): announcement, voucher, num_passes, + public_key, ): """ The ``ZKAPAuthorizerStorageServer`` returned by ``get_storage_client`` @@ -494,7 +497,7 @@ class ClientPluginTests(TestCase): controller = PaymentController( store, - DummyRedeemer(), + DummyRedeemer(public_key), default_token_count=num_passes, num_redemption_groups=1, clock=Clock(),