diff --git a/src/_zkapauthorizer/_plugin.py b/src/_zkapauthorizer/_plugin.py index 1ce34fed2774c545296ecf5ad330a32c8c3e2281..a72586d8e69bb66c8eec1913c2a7c0e71aea90f7 100644 --- a/src/_zkapauthorizer/_plugin.py +++ b/src/_zkapauthorizer/_plugin.py @@ -142,10 +142,10 @@ class ZKAPAuthorizer(object): kwargs = configuration.copy() root_url = kwargs.pop(u"ristretto-issuer-root-url") pass_value = int(kwargs.pop(u"pass-value", BYTES_PER_PASS)) - signing_key = SigningKey.decode_base64( + signing_key = load_signing_key( FilePath( kwargs.pop(u"ristretto-signing-key-path"), - ).getContent(), + ), ) announcement = { u"ristretto-issuer-root-url": root_url, @@ -288,3 +288,23 @@ def get_root_nodes(client_node, node_config): return [] else: return [client_node.create_node_from_uri(rootcap)] + + +def load_signing_key(path): + """ + Read a serialized Ristretto signing key from the given path and return it + as a ``challenge_bypass_ristretto.SigningKey``. + + Unlike ``challenge_bypass_ristretto.SigningKey.decode_base64`` this + function will clean up any whitespace around the key. + + :param FilePath path: The path from which to read the key. + + :raise challenge_bypass_ristretto.DecodeException: If + ``SigningKey.decode_base64`` raises this exception it will be passed + through. + + :return challenge_bypass_ristretto.SigningKey: An object representing the + key read. + """ + return SigningKey.decode_base64(path.getContent().strip()) diff --git a/src/_zkapauthorizer/tests/strategies.py b/src/_zkapauthorizer/tests/strategies.py index fde7590c73ab26ee8ac784fc3955baa1bdb736df..77c854a2bff7109130f8b41257fbca9671ce3262 100644 --- a/src/_zkapauthorizer/tests/strategies.py +++ b/src/_zkapauthorizer/tests/strategies.py @@ -35,6 +35,7 @@ from zope.interface import ( from hypothesis.strategies import ( one_of, + sampled_from, just, none, binary, @@ -915,3 +916,33 @@ def api_auth_tokens(): authorization tokens. """ return binary(min_size=32, max_size=32).map(b64encode) + + +def ristretto_signing_keys(): + """ + Build byte strings holding base64-encoded Ristretto signing keys, perhaps + with leading or trailing whitespace. + """ + keys = sampled_from([ + # A few legit keys + b"mkQf85V2vyLQRUYuqRb+Ke6K+M9pOtXm4MslsuCdBgg=", + b"6f93OIdZHHAmSIaRXDSIU1UcN+sbDAh41TRPb5DhrgI=", + b"k58h8yPT18epw+EKMJhwHFfoM6r3TIExKm4efQHNBgM=", + b"rbaAlWZ3NCnl5oZ9meviGfpLbyJpgpuiuFOX0rLnNwQ=", + ]) + whitespace = sampled_from([ + # maybe no whitespace at all + b"" + # or maybe some + b" ", + b"\t", + b"\n", + b"\r\n", + ]) + + return builds( + lambda leading, key, trailing: leading + key + trailing, + whitespace, + keys, + whitespace, + ) diff --git a/src/_zkapauthorizer/tests/test_plugin.py b/src/_zkapauthorizer/tests/test_plugin.py index 0349dde406b8b78cc06b452b57ac8647d6a7b2dd..1d460324c840460b7c5162bdea0ae677adb96d39 100644 --- a/src/_zkapauthorizer/tests/test_plugin.py +++ b/src/_zkapauthorizer/tests/test_plugin.py @@ -110,6 +110,10 @@ from twisted.plugins.zkapauthorizer import ( storage_server, ) +from challenge_bypass_ristretto import ( + SigningKey, +) + from ..spending import ( GET_PASSES, ) @@ -134,6 +138,10 @@ from ..lease_maintenance import ( SERVICE_NAME, ) +from .._plugin import ( + load_signing_key, +) + from .strategies import ( minimal_tahoe_configs, tahoe_configs, @@ -147,6 +155,7 @@ from .strategies import ( sharenum_sets, sizes, pass_counts, + ristretto_signing_keys, ) from .matchers import ( Provides, @@ -662,3 +671,22 @@ class LeaseMaintenanceServiceTests(TestCase): been written to the client's configuration directory. """ return self._created_test(get_config, servers_yaml, rootcap=False) + + +class LoadSigningKeyTests(TestCase): + """ + Tests for ``load_signing_key``. + """ + @given(ristretto_signing_keys()) + def test_valid(self, key_bytes): + """ + A base64-encoded byte string representing a valid Ristretto signing key + can be loaded from a file into a ``SigningKey`` object using + ``load_signing_key``. + + :param bytes key: A base64-encoded Ristretto signing key. + """ + p = FilePath(self.useFixture(TempDir()).join(b"key")) + p.setContent(key_bytes) + key = load_signing_key(p) + self.assertThat(key, IsInstance(SigningKey)) diff --git a/src/_zkapauthorizer/tests/testing-signing.key b/src/_zkapauthorizer/tests/testing-signing.key index 078e2134bd719832938b965b0288e4246c364d2d..829dff84ace4eb48281135c5f4e2a63b2bd79879 100644 --- a/src/_zkapauthorizer/tests/testing-signing.key +++ b/src/_zkapauthorizer/tests/testing-signing.key @@ -1 +1,2 @@ -mkQf85V2vyLQRUYuqRb+Ke6K+M9pOtXm4MslsuCdBgg= \ No newline at end of file +mkQf85V2vyLQRUYuqRb+Ke6K+M9pOtXm4MslsuCdBgg= +