From 8f95cf9918f3e529a03cc54338b149d66b753104 Mon Sep 17 00:00:00 2001
From: Jean-Paul Calderone <exarkun@twistedmatrix.com>
Date: Mon, 2 Mar 2020 14:42:38 -0500
Subject: [PATCH] Unblinded tokens are 128 bytes of base64

---
 src/_zkapauthorizer/controller.py       | 11 ++++++++---
 src/_zkapauthorizer/model.py            | 14 ++++++++++----
 src/_zkapauthorizer/tests/strategies.py | 11 +++++++----
 3 files changed, 25 insertions(+), 11 deletions(-)

diff --git a/src/_zkapauthorizer/controller.py b/src/_zkapauthorizer/controller.py
index 2b00511..32a52e9 100644
--- a/src/_zkapauthorizer/controller.py
+++ b/src/_zkapauthorizer/controller.py
@@ -35,6 +35,7 @@ from datetime import (
 )
 from base64 import (
     b64encode,
+    b64decode,
 )
 import attr
 
@@ -301,9 +302,13 @@ class DummyRedeemer(object):
         :return: An already-fired ``Deferred`` that has a list of
           ``UnblindedToken`` instances wrapping meaningless values.
         """
+        def dummy_unblinded_token(random_token):
+            random_value = b64decode(random_token.token_value.encode("ascii"))
+            unblinded_value = random_value + b"x" * (96 - len(random_value))
+            return UnblindedToken(b64encode(unblinded_value).decode("ascii"))
         return succeed(
             list(
-                UnblindedToken(token.token_value)
+                dummy_unblinded_token(token)
                 for token
                 in random_tokens
             ),
@@ -311,7 +316,7 @@ class DummyRedeemer(object):
 
     def tokens_to_passes(self, message, unblinded_tokens):
         return list(
-            Pass(token.text)
+            Pass(token.unblinded_token)
             for token
             in unblinded_tokens
         )
@@ -463,7 +468,7 @@ class RistrettoRedeemer(object):
         assert isinstance(unblinded_tokens, list)
         assert all(isinstance(element, UnblindedToken) for element in unblinded_tokens)
         unblinded_tokens = list(
-            privacypass.UnblindedToken.decode_base64(token.text.encode("ascii"))
+            privacypass.UnblindedToken.decode_base64(token.unblinded_token.encode("ascii"))
             for token
             in unblinded_tokens
         )
diff --git a/src/_zkapauthorizer/model.py b/src/_zkapauthorizer/model.py
index 9088f04..7b309a3 100644
--- a/src/_zkapauthorizer/model.py
+++ b/src/_zkapauthorizer/model.py
@@ -371,7 +371,7 @@ class VoucherStore(object):
             INSERT INTO [unblinded-tokens] VALUES (?)
             """,
             list(
-                (t.text,)
+                (t.unblinded_token,)
                 for t
                 in unblinded_tokens
             ),
@@ -646,12 +646,18 @@ class UnblindedToken(object):
     and can be used to construct a privacy-preserving pass which can be
     exchanged for service.
 
-    :ivar unicode text: The base64 encoded serialized form of the unblinded
-        token.  This can be used to reconstruct a
+    :ivar unicode unblinded_token: The base64 encoded serialized form of the
+        unblinded token.  This can be used to reconstruct a
         ``privacypass.UnblindedToken`` using that class's ``decode_base64``
         method.
     """
-    text = attr.ib(validator=attr.validators.instance_of(unicode))
+    unblinded_token = attr.ib(
+        validator=attr.validators.and_(
+            attr.validators.instance_of(unicode),
+            is_base64_encoded(),
+            has_length(128),
+        ),
+    )
 
 
 @attr.s(frozen=True)
diff --git a/src/_zkapauthorizer/tests/strategies.py b/src/_zkapauthorizer/tests/strategies.py
index c0953e4..d62b840 100644
--- a/src/_zkapauthorizer/tests/strategies.py
+++ b/src/_zkapauthorizer/tests/strategies.py
@@ -83,6 +83,8 @@ from ..model import (
 
 # The length of a `Token`, in bytes.
 _TOKEN_LENGTH = 96
+# The length of a `UnblindedToken`, in bytes.
+_UNBLINDED_TOKEN_LENGTH = 96
 
 def _merge_dictionaries(dictionaries):
     result = {}
@@ -356,11 +358,12 @@ def unblinded_tokens():
     base64 encode data.  You cannot use these in the PrivacyPass cryptographic
     protocol but you can put them into the database and take them out again.
     """
-    return binary(
-        min_size=32,
-        max_size=32,
+    return byte_strings(
+        label=b"unblinded-tokens",
+        length=_UNBLINDED_TOKEN_LENGTH,
+        entropy=4,
     ).map(
-        urlsafe_b64encode,
+        b64encode,
     ).map(
         lambda zkap: UnblindedToken(zkap.decode("ascii")),
     )
-- 
GitLab