diff --git a/src/_zkapauthorizer/_storage_server.py b/src/_zkapauthorizer/_storage_server.py index 6d03290e7a3dc719a1c2c5fee00df03cdaae179d..8bae23d92c31d59ee4405d3ccfca4c0c09f30d51 100644 --- a/src/_zkapauthorizer/_storage_server.py +++ b/src/_zkapauthorizer/_storage_server.py @@ -49,7 +49,7 @@ from privacypass import ( SigningKey, ) from .foolscap import ( - RITokenAuthorizedStorageServer, + RIPrivacyPassAuthorizedStorageServer, ) from .storage_common import ( BYTES_PER_PASS, @@ -61,6 +61,16 @@ from .storage_common import ( ) class MorePassesRequired(Exception): + """ + Storage operations fail with ``MorePassesRequired`` when they are not + accompanied by a sufficient number of valid passes. + + :ivar int valid_count: The number of valid passes presented in the + operation. + + ivar int required_count: The number of valid passes which must be + presented for the operation to be authorized. + """ def __init__(self, valid_count, required_count): self.valid_count = valid_count self.required_count = required_count @@ -75,7 +85,7 @@ class MorePassesRequired(Exception): return repr(self) -@implementer_only(RITokenAuthorizedStorageServer, IReferenceable, IRemotelyCallable) +@implementer_only(RIPrivacyPassAuthorizedStorageServer, IReferenceable, IRemotelyCallable) # It would be great to use `frozen=True` (value-based hashing) instead of # `cmp=False` (identity based hashing) but Referenceable wants to set some # attributes on self and it's hard to avoid that. @@ -90,12 +100,13 @@ class ZKAPAuthorizerStorageServer(Referenceable): def _is_invalid_pass(self, message, pass_): """ - Check the validity of a single pass. + Cryptographically check the validity of a single pass. :param unicode message: The shared message for pass validation. :param bytes pass_: The encoded pass to validate. - :return bool: ``True`` if the pass is invalid, ``False`` otherwise. + :return bool: ``False`` (invalid) if the pass includes a valid + signature, ``True`` (valid) otherwise. """ assert isinstance(message, unicode), "message %r not unicode" % (message,) assert isinstance(pass_, bytes), "pass %r not bytes" % (pass_,) diff --git a/src/_zkapauthorizer/foolscap.py b/src/_zkapauthorizer/foolscap.py index 213eca4476aed796fa4ba166014a7e4fcd6a75ac..948aff38eb681754af6e24e57d5252ce34254196 100644 --- a/src/_zkapauthorizer/foolscap.py +++ b/src/_zkapauthorizer/foolscap.py @@ -18,34 +18,32 @@ from allmydata.interfaces import ( ) # The Foolscap convention seems to be to try to constrain inputs to valid -# values. So we'll try to limit the number of tokens a client can supply. +# values. So we'll try to limit the number of passes a client can supply. # Foolscap may be moving away from this so we may eventually drop this as # well. Though it may still make sense on a non-Foolscap protocol (eg HTTP) # which Tahoe-LAFS may eventually support. # # In any case, for now, pick some fairly arbitrary value. I am deliberately # picking a small number here and expect to have to raise. However, ideally, -# a client could accomplish a lot with a few tokens while also not wasting a +# a client could accomplish a lot with a few passes while also not wasting a # lot of value. -MAXIMUM_TOKENS_PER_CALL = 10 +_MAXIMUM_PASSES_PER_CALL = 10 -# This is the length of a serialized Ristretto-flavored PrivacyPass pass -# (there's a lot of confusion between "tokens" and "passes" here, sadly). -# -# The pass is a combination of base64-encoded token preimages and unblinded -# token signatures. -TOKEN_LENGTH = 177 +# This is the length of a serialized Ristretto-flavored PrivacyPass pass The +# pass is a combination of token preimages and unblinded token signatures, +# each base64-encoded. +_PASS_LENGTH = 177 # Take those values and turn them into the appropriate Foolscap constraint # objects. Foolscap seems to have a convention of representing these as # CamelCase module-level values so I replicate that here. -Token = ByteStringConstraint(maxLength=TOKEN_LENGTH, minLength=TOKEN_LENGTH) -TokenList = ListOf(Token, maxLength=MAXIMUM_TOKENS_PER_CALL) +_Pass = ByteStringConstraint(maxLength=_PASS_LENGTH, minLength=_PASS_LENGTH) +_PassList = ListOf(_Pass, maxLength=_MAXIMUM_PASSES_PER_CALL) -def add_tokens(schema): +def add_passes(schema): """ - Add a ``tokens`` parameter to the given method schema. + Add a ``passes`` parameter to the given method schema. :param foolscap.remoteinterface.RemoteMethodSchema schema: An existing method schema to modify. @@ -53,7 +51,7 @@ def add_tokens(schema): :return foolscap.remoteinterface.RemoteMethodSchema: A schema like ``schema`` but with one additional required argument. """ - return add_arguments(schema, [(b"tokens", TokenList)]) + return add_arguments(schema, [(b"passes", _PassList)]) def add_arguments(schema, kwargs): @@ -88,33 +86,33 @@ def add_arguments(schema, kwargs): -class RITokenAuthorizedStorageServer(RemoteInterface): +class RIPrivacyPassAuthorizedStorageServer(RemoteInterface): """ - An object which can store and retrieve shares, subject to token-based + An object which can store and retrieve shares, subject to pass-based authorization. This is much the same as ``allmydata.interfaces.RIStorageServer`` but - several of its methods take an additional ``tokens`` parameter. Clients - are expected to supply suitable tokens and only after the tokens have been + several of its methods take an additional ``passes`` parameter. Clients + are expected to supply suitable passes and only after the passes have been validated is service provided. """ __remote_name__ = ( - "RITokenAuthorizedStorageServer.tahoe.privatestorage.io" + "RIPrivacyPassAuthorizedStorageServer.tahoe.privatestorage.io" ) get_version = RIStorageServer["get_version"] - allocate_buckets = add_tokens(RIStorageServer["allocate_buckets"]) + allocate_buckets = add_passes(RIStorageServer["allocate_buckets"]) - add_lease = add_tokens(RIStorageServer["add_lease"]) + add_lease = add_passes(RIStorageServer["add_lease"]) - renew_lease = add_tokens(RIStorageServer["renew_lease"]) + renew_lease = add_passes(RIStorageServer["renew_lease"]) get_buckets = RIStorageServer["get_buckets"] slot_readv = RIStorageServer["slot_readv"] - slot_testv_and_readv_and_writev = add_tokens( + slot_testv_and_readv_and_writev = add_passes( RIStorageServer["slot_testv_and_readv_and_writev"], ) diff --git a/src/_zkapauthorizer/storage_common.py b/src/_zkapauthorizer/storage_common.py index 955eb59928c337a4ada4881cf23f92977904dc57..ef1215d9dceacb3a28de204388e76dc604283ca9 100644 --- a/src/_zkapauthorizer/storage_common.py +++ b/src/_zkapauthorizer/storage_common.py @@ -1,3 +1,20 @@ +# Copyright 2019 PrivateStorage.io, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Functionality shared between the storage client and server. +""" from base64 import ( b64encode, @@ -15,6 +32,8 @@ def _message_maker(label): ) return make_message +# Functions to construct the PrivacyPass request-binding message for pass +# construction for different Tahoe-LAFS storage operations. allocate_buckets_message = _message_maker(u"allocate_buckets") add_lease_message = _message_maker(u"add_lease") renew_lease_message = _message_maker(u"renew_lease") @@ -29,10 +48,13 @@ def required_passes(bytes_per_pass, share_nums, share_size): Calculate the number of passes that are required to store ``stored_bytes`` for one lease period. - :param int stored_bytes: A number of bytes of storage for which to - calculate a price in passes. + :param int bytes_per_pass: The number of bytes the storage of which for + one lease period one pass covers. - :return int: The number of passes. + :param set[int] share_nums: The share numbers which will be stored. + :param int share_size: THe number of bytes in a single share. + + :return int: The number of passes required to cover the storage cost. """ return int( ceil( diff --git a/src/_zkapauthorizer/tests/fixtures.py b/src/_zkapauthorizer/tests/fixtures.py index 20b05922495aefce85e12a69941276836840950a..dbb49e6568cae998024e71c866e67e40c88b089f 100644 --- a/src/_zkapauthorizer/tests/fixtures.py +++ b/src/_zkapauthorizer/tests/fixtures.py @@ -1,3 +1,21 @@ +# Copyright 2019 PrivateStorage.io, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Common fixtures to let the test suite focus on application logic. +""" + from __future__ import ( absolute_import, ) diff --git a/src/_zkapauthorizer/tests/matchers.py b/src/_zkapauthorizer/tests/matchers.py index c54c99067b80438f119edbc9521b9d1e84d87499..bcb4edbd37f4e8a5642405cf070e20f1d805b213 100644 --- a/src/_zkapauthorizer/tests/matchers.py +++ b/src/_zkapauthorizer/tests/matchers.py @@ -47,7 +47,7 @@ def matches_version_dictionary(): """ Match the dictionary returned by Tahoe-LAFS' ``RIStorageServer.get_version`` which is also the dictionary returned by - our own ``RITokenAuthorizedStorageServer.get_version``. + our own ``RIPrivacyPassAuthorizedStorageServer.get_version``. """ return ContainsDict({ # It has these two top-level keys, at least. Try not to be too diff --git a/src/_zkapauthorizer/tests/privacypass.py b/src/_zkapauthorizer/tests/privacypass.py index 9b46fe3832bf1ce57a6852037e9bca5c3fb1fb37..63c1f4386652cc8cb5432e0d522966878c7f926b 100644 --- a/src/_zkapauthorizer/tests/privacypass.py +++ b/src/_zkapauthorizer/tests/privacypass.py @@ -1,3 +1,21 @@ +# Copyright 2019 PrivateStorage.io, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Ristretto-flavored PrivacyPass helpers for the test suite. +""" + from __future__ import ( absolute_import, ) @@ -8,6 +26,21 @@ from privacypass import ( ) def make_passes(signing_key, for_message, random_tokens): + """ + Create a number of cryptographically correct privacy passes. + + :param privacypass.SigningKey signing_key: The key to use to sign the + passes. + + :param unicode for_message: The request-binding message with which to + associate the passes. + + :param list[privacypass.RandomToken] random_tokens: The random tokens to + feed in to the pass generation process. + + :return list[unicode]: The privacy passes. The returned list has one + element for each element of ``random_tokens``. + """ blinded_tokens = list( token.blind() for token diff --git a/src/_zkapauthorizer/tests/strategies.py b/src/_zkapauthorizer/tests/strategies.py index 5a0c531a6448bda2c59d40deeb9aa9e3523be77e..fbbb5c0e3085c1a171c8666154a1b46628edb48b 100644 --- a/src/_zkapauthorizer/tests/strategies.py +++ b/src/_zkapauthorizer/tests/strategies.py @@ -220,8 +220,10 @@ def zkaps(): """ return builds( lambda preimage, signature: Pass(u"{} {}".format(preimage, signature)), - preimage=binary(min_size=66, max_size=66).map(urlsafe_b64encode), - signature=binary(min_size=66, max_size=66).map(urlsafe_b64encode), + # Sizes informed by + # https://github.com/brave-intl/challenge-bypass-ristretto/blob/2f98b057d7f353c12b2b12d0f5ae9ad115f1d0ba/src/oprf.rs#L18-L33 + preimage=binary(min_size=64, max_size=64).map(urlsafe_b64encode), + signature=binary(min_size=64, max_size=64).map(urlsafe_b64encode), ) diff --git a/src/_zkapauthorizer/tests/test_storage_server.py b/src/_zkapauthorizer/tests/test_storage_server.py index 32ebcc42e78056a39926ee5650b99d74503eeb79..ad8ba0eb5fcaa615cfb39b7b7056c963295d7048 100644 --- a/src/_zkapauthorizer/tests/test_storage_server.py +++ b/src/_zkapauthorizer/tests/test_storage_server.py @@ -1,3 +1,21 @@ +# Copyright 2019 PrivateStorage.io, LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Tests for ``_zkapauthorizer._storage_server``. +""" + from __future__ import ( absolute_import, division,