Extend MetaFile and TargetFile classes with methods
for length and hash verification. The common functionality
is implemented as static methods of the base class while
MetaFile and TargetFile implement the user API based on it.
Define LengthOrHasheMismathError.
Signed-off-by: Teodora Sechkova <tsechkova@vmware.com>
This is likely not needed by users of the API (as they are interested
in the higher level functionality "verify delegate metadata with
threshold of signatures").
Moving verify to Key makes the API cleaner because including both
"verify myself" and "verify a delegate with threshold" can look awkward
in Metadata, and because the ugly Securesystemslib integration is now
Key class implementation detail (see Key.to_securesystemslib_key()).
Also raise on verify failure instead of returning false: this was found
to confuse API users (and was arguably not a pythonic way to handle it).
* Name the function verify_signature() to make it clear what is being
verified.
* Assume only one signature per keyid exists: see #1422
* Raise only UnsignedMetadataError (when no signatures or verify failure),
the remaining lower level errors will be handled in #1351
* Stop using a "keystore" in tests for the public keys: everything we
need is in metadata already
This changes API, but also should not be something API users want to
call in the future when "verify a delegate with threshold" exists.
Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
This simplifies life for API users as usually a key needs its
identifier: this is already visible in how update() becomes simpler
in the API.
The downside is that 'from_dict()' now has two arguments (so arguably
the name is not great anymore but it still does _mostly_ the same job
as other from_dicts).
This is an API change, if a minor one.
Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
Currently we have one use of tuf/formats.py in tuf/api/metadata.py.
If we do the conversion of the expires string in metadata.py,
we can keep the two implementations separate.
Signed-off-by: Velichka Atanasova <avelichka@vmware.com>
Without this mypy figures the dict is Dict[str, str] and then promptly
fails when int value is inserted
Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
Also define from_dict()/to_dict() as abstract: this helps mypy keep
track of things. Rename derived argument *_dict in the derived classes
to keep the linter happy.
Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
The import is useful for mypy so it can check the types.
Add a pylint disable just like json.py does in the same situation.
Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
Currently, when we call Targets/Snapshot/Timestamp.update() we are
passing all of the necessary values to create MetaFile/Targets File
respectively.
This is not needed, given that one of the reasons we have created
MetaFile and TargetFile is to make the API easier to use.
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
Disable the "C0302: Too many lines in module" warning which warns for modules
with more 1000 lines, because all of the code here is logically connected
and currently, we are above 1000 lines by a small margin.
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
In the top-level metadata classes, there are complex attributes such as
"meta" in Targets and Snapshot, "key" and "roles" in Root etc.
We want to represent those complex attributes with a class to allow
easier verification and support for metadata with unrecognized fields.
For more context read ADR 0004 and ADR 0008 in the docs/adr folder.
As written in the spec "targets" in "targets.json" has defined the
"custom" field serving the same purpose as "unrecognized_fields" in the
implementation.
That's why to conform against the spec and support "custom" and allow
"unrecognized_fields" everywhere where it's not sensitive we can define
custom as property which actually access data stored in
unrecognized_fields.
For context read ADR 8 in tuf/docs/adr.
Additionally, after adding the TargetFile class, when we create a
Targets an object we are now calling from dict twice - one for the main
Targets class and one for each of the complex attributes
TargetFile.from_dict() and Delegations.from_dict().
Given that the "from_dict" methods have the side effect of destroying
the given dictionary, we would need to start using deepcopy()
for our tests.
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
In the top-level metadata classes, there are complex attributes such as
"meta" in Targets and Snapshot, "key" and "roles" in Root etc.
We want to represent those complex attributes with a class to allow
easier verification and support for metadata with unrecognized fields.
For more context read ADR 0004 and ADR 0008 in the docs/adr folder.
Additionally, after adding the MetaFile class, when we create an object
we are now calling from dict twice - one for the main class (Timestamp,
Snapshot) and one for the pacticular complex attribute -
MetaFile.from_dict(). Given that the "from_dict" methods have the
side effect of destroying the given dictionary, we would need to
start using deepcopy() for our tests.
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
NOTE: making consistent_snapshot optional requires using a default value
for the argument in __init__ in Root and thus consistent_snapshot should
be rearranged in the end.
Read more: https://github.com/theupdateframework/tuf/pull/1394#issuecomment-842134961
From chapter 7 in the spec (version 1.0.17)
"Finally, the root metadata should write the Boolean
"consistent_snapshot" attribute at the root level of its keys of
attributes.
If consistent snapshots are not written by the repository,
then the attribute may either be left unspecified or be set to the
False value. Otherwise, it must be set to the True value."
We want to make sure we support repositories
without consistent_snapshot set.
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
Use either "if X is not None:" or a try-except instead of a "if X:".
I believe Targets.from_dict() was not really broken with previous code
but it looks suspicious and did fail the added test with a strange
exception: I expect the from_dict() methods to mainly fail with
KeyErrors, ValueErrors or AttributeErrors if file format structure
is incorrect.
Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
A DelegatedRole with paths=[] fails to serialize correctly (paths is not
included in the output json).
Fix the issue, modify tests to notice a regression.
Fixes#1389
Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
After the implementation of a Key class representing
the public portion of a key, the method add_key() should
take an argument of type Key, instead of a dictionary.
Test cases are updated accordingly.
Signed-off-by: Teodora Sechkova <tsechkova@vmware.com>
Stop using Mapping where we actually mean Dict:
Mapping means "we only need a read-only dict" and most of the time
this is not really the case.
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
In the top level metadata classes, there are complex attributes such as
"meta" in Targets and Snapshot, "key" and "roles" in Root etc.
We want to represent those complex attributes with a class to allow
easier verification and support for metadata with unrecognized fields.
For more context read ADR 0004 and ADR 0008 in the docs/adr folder.
DelegatedRole shares a couple of fields with the Role class and that's
why it inherits it.
I decided to use a separate Delegations class because I thought it will
make it easier to read, verify and add additional helper functions.
Also, I tried to make sure that I test each level of the delegations
representation for support of storing unrecognized fields.
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
This is suggested by the Google style guide: the old style logging
(%-format) allows the log strings to be lazily formatted so there's less
need to think about performance when forming debug messages.
No actual code changes are needed because the metadata API does not yet
log anything.
Fixes#1334
Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
* Define missing argument type hints
* Stop using Mapping where we actually mean Dict:
Mapping means "we only need a read-only dict" and most of the
time this is not really the case.
* Use List, not list (latter only works from Python 3.9)
* Update Metadata.signatures documentation
Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
We should not do multiple lookups through data structures if one is
enough (here we have extra lookups on both roles and keyids).
Also in this case raising on missing key seems like the preferable
alternative so even a try-except is not needed.
Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
As per the specification (v1.0.1) length and hashes fields
in timestamp and snapshot metadata are optional.
We have implement this in the older API
(see https://github.com/theupdateframework/tuf/pull/1031) and we should
implement it in the new API.
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
Using Metadata APIs '_type' attribute (from outside metadata.py) currently
results in linter errors.
Add a duplicate 'type': this way the API users can avoid linter errors
but '_type' is still available in case the strict file format
compatibility is needed.
Fixes#1375
Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
Also remove _type from Signed constructor arguments: the value is in a
class atttribute. This way _type never needs to be validated (except
in the dispatcher in Metadata). There is a double-check in
_common_fields_from_dict() just to be sure.
This makes the API easier to use correctly as the public property is
immutable.
This is an API break as all Signed constructors change -- this could be
avoided but seems like the correct choice.
Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
Verify that adding an already existing key to keyid for a particular
role in Root won't create duplicate key.
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
From the specification:
"Clients MUST ensure that for any KEYID represented in this key list
and in other files, only one unique key has that KEYID."
The “only one unique key has that KEYID” is a requirement which can’t
be achieved if two keyids are the same.
So, in order to mandate that requirement it makes sense to use a set
which will guarantee us the keyid’s uniqueness.
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
In the top level metadata classes, there are complex attributes such as
"meta" in Targets and Snapshot, "key" and "roles" in Root etc.
We want to represent those complex attributes with a class to allow
easier verification and support for metadata with unrecognized fields.
For more context read ADR 0004 and ADR 0008 in the docs/adr folder.
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
In the top level metadata classes, there are complex attributes such as
"meta" in Targets and Snapshot, "key" and "roles" in Root etc.
We want to represent those complex attributes with a class to allow
easier verification and support for metadata with unrecognized fields.
For more context read ADR 0004 and ADR 0008 in the docs/adr folder.
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
Black was updated from 20.8b1 to 21.4b0 requiring that one-line
docstring don't add additional space before the closing quotes.
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
In order to support ADR 0008 we would want to accept unrecognized
fields in all metadata classes.
Input that contains unknown fields in the 'signed' dictionary should
successfully deserialize into a Metadata object, and that object should
successfully serialize with the unknown fields intact.
Also, we should test that we support unrecognized fields when adding
new classes or modifying existing ones to make sure we support
ADR 0008.
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
This is essentially short-hand for
JSONDeserializer().deserialize(data)
but seems much easier for the API user so may be worth it.
Metadata.from_file() now uses Metadata.from_bytes() internally.
Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
Checks metadata expiration against a reference time (a naive datetime in UTC).
If not provided, checks against the current UTC date and time.
Returns True if expiration time is less than the reference time.
Signed-off-by: Velichka Atanasova <avelichka@vmware.com>