| 1 | """ |
|---|
| 2 | Ported to Python 3. |
|---|
| 3 | """ |
|---|
| 4 | |
|---|
| 5 | from typing import Union |
|---|
| 6 | |
|---|
| 7 | import attr |
|---|
| 8 | |
|---|
| 9 | from nacl.hash import blake2b |
|---|
| 10 | from nacl.encoding import RawEncoder |
|---|
| 11 | |
|---|
| 12 | from .lease import ( |
|---|
| 13 | LeaseInfo, |
|---|
| 14 | HashedLeaseInfo, |
|---|
| 15 | ) |
|---|
| 16 | |
|---|
| 17 | @attr.s(frozen=True) |
|---|
| 18 | class CleartextLeaseSerializer: |
|---|
| 19 | """ |
|---|
| 20 | Serialize and unserialize leases with cleartext secrets. |
|---|
| 21 | """ |
|---|
| 22 | _to_data = attr.ib() |
|---|
| 23 | _from_data = attr.ib() |
|---|
| 24 | |
|---|
| 25 | def serialize(self, lease): |
|---|
| 26 | # type: (LeaseInfo) -> bytes |
|---|
| 27 | """ |
|---|
| 28 | Represent the given lease as bytes with cleartext secrets. |
|---|
| 29 | """ |
|---|
| 30 | if isinstance(lease, LeaseInfo): |
|---|
| 31 | return self._to_data(lease) |
|---|
| 32 | raise ValueError( |
|---|
| 33 | "ShareFile v1 schema only supports LeaseInfo, not {!r}".format( |
|---|
| 34 | lease, |
|---|
| 35 | ), |
|---|
| 36 | ) |
|---|
| 37 | |
|---|
| 38 | def unserialize(self, data): |
|---|
| 39 | # type: (bytes) -> LeaseInfo |
|---|
| 40 | """ |
|---|
| 41 | Load a lease with cleartext secrets from the given bytes representation. |
|---|
| 42 | """ |
|---|
| 43 | # In v1 of the immutable schema lease secrets are stored plaintext. |
|---|
| 44 | # So load the data into a plain LeaseInfo which works on plaintext |
|---|
| 45 | # secrets. |
|---|
| 46 | return self._from_data(data) |
|---|
| 47 | |
|---|
| 48 | @attr.s(frozen=True) |
|---|
| 49 | class HashedLeaseSerializer: |
|---|
| 50 | _to_data = attr.ib() |
|---|
| 51 | _from_data = attr.ib() |
|---|
| 52 | |
|---|
| 53 | @classmethod |
|---|
| 54 | def _hash_secret(cls, secret): |
|---|
| 55 | # type: (bytes) -> bytes |
|---|
| 56 | """ |
|---|
| 57 | Hash a lease secret for storage. |
|---|
| 58 | """ |
|---|
| 59 | return blake2b(secret, digest_size=32, encoder=RawEncoder) |
|---|
| 60 | |
|---|
| 61 | @classmethod |
|---|
| 62 | def _hash_lease_info(cls, lease_info): |
|---|
| 63 | # type: (LeaseInfo) -> HashedLeaseInfo |
|---|
| 64 | """ |
|---|
| 65 | Hash the cleartext lease info secrets into a ``HashedLeaseInfo``. |
|---|
| 66 | """ |
|---|
| 67 | if not isinstance(lease_info, LeaseInfo): |
|---|
| 68 | # Provide a little safety against misuse, especially an attempt to |
|---|
| 69 | # re-hash an already-hashed lease info which is represented as a |
|---|
| 70 | # different type. |
|---|
| 71 | raise TypeError( |
|---|
| 72 | "Can only hash LeaseInfo, not {!r}".format(lease_info), |
|---|
| 73 | ) |
|---|
| 74 | |
|---|
| 75 | # Hash the cleartext secrets in the lease info and wrap the result in |
|---|
| 76 | # a new type. |
|---|
| 77 | return HashedLeaseInfo( |
|---|
| 78 | attr.assoc( |
|---|
| 79 | lease_info, |
|---|
| 80 | renew_secret=cls._hash_secret(lease_info.renew_secret), |
|---|
| 81 | cancel_secret=cls._hash_secret(lease_info.cancel_secret), |
|---|
| 82 | ), |
|---|
| 83 | cls._hash_secret, |
|---|
| 84 | ) |
|---|
| 85 | |
|---|
| 86 | def serialize(self, lease: Union[LeaseInfo, HashedLeaseInfo]) -> bytes: |
|---|
| 87 | if isinstance(lease, LeaseInfo): |
|---|
| 88 | # v2 of the immutable schema stores lease secrets hashed. If |
|---|
| 89 | # we're given a LeaseInfo then it holds plaintext secrets. Hash |
|---|
| 90 | # them before trying to serialize. |
|---|
| 91 | lease = self._hash_lease_info(lease) |
|---|
| 92 | if isinstance(lease, HashedLeaseInfo): |
|---|
| 93 | return self._to_data(lease) |
|---|
| 94 | raise ValueError( |
|---|
| 95 | "ShareFile v2 schema cannot represent lease {!r}".format( |
|---|
| 96 | lease, |
|---|
| 97 | ), |
|---|
| 98 | ) |
|---|
| 99 | |
|---|
| 100 | def unserialize(self, data): |
|---|
| 101 | # type: (bytes) -> HashedLeaseInfo |
|---|
| 102 | # In v2 of the immutable schema lease secrets are stored hashed. Wrap |
|---|
| 103 | # a LeaseInfo in a HashedLeaseInfo so it can supply the correct |
|---|
| 104 | # interpretation for those values. |
|---|
| 105 | return HashedLeaseInfo(self._from_data(data), self._hash_secret) |
|---|
| 106 | |
|---|
| 107 | v1_immutable = CleartextLeaseSerializer( |
|---|
| 108 | LeaseInfo.to_immutable_data, |
|---|
| 109 | LeaseInfo.from_immutable_data, |
|---|
| 110 | ) |
|---|
| 111 | |
|---|
| 112 | v2_immutable = HashedLeaseSerializer( |
|---|
| 113 | HashedLeaseInfo.to_immutable_data, |
|---|
| 114 | LeaseInfo.from_immutable_data, |
|---|
| 115 | ) |
|---|
| 116 | |
|---|
| 117 | v1_mutable = CleartextLeaseSerializer( |
|---|
| 118 | LeaseInfo.to_mutable_data, |
|---|
| 119 | LeaseInfo.from_mutable_data, |
|---|
| 120 | ) |
|---|
| 121 | |
|---|
| 122 | v2_mutable = HashedLeaseSerializer( |
|---|
| 123 | HashedLeaseInfo.to_mutable_data, |
|---|
| 124 | LeaseInfo.from_mutable_data, |
|---|
| 125 | ) |
|---|