- valid length: greater than zero
- valid hashes: a non-empty dictionary of type Dict[str, str]
Checking the validity of hash algorithms is not part
of the metadata input validation and is done by
securesystemslib during hash verification.
Signed-off-by: Teodora Sechkova <tsechkova@vmware.com>
Clarify that we don't semantically validate "Key" instances during
initialization and that this is a responsibility of securesystemslib.
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
Probably there could be future API calls that modify "threshold"
to a new value, but the problem is we don't have a clear idea
if they would exist and what exactly they will do.
That's why it makes sense to validate against the potential problems
we can imagine - in this case, is passing a threshold below 1.
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
In our discussion with Jussi we come to the conclusion that we want
to verify that all Key attributes contain values in the expected types,
but at the same time, we don't want to focus on validating the semantics
behind them.
The reason is that having a Key instance with invalid attributes is
possible and supported by the spec.
That's why we have a "threshold" for the roles meaning we can have up to
a certain number of invalid Keys until we satisfy
the required threshold.
Also, for deeper semantic validation it's better to be done in
securesystemslib which does the actual work with keys.
For context see: https://github.com/theupdateframework/tuf/issues/1438
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
According to point 2 in the semver specification:
"A normal version number MUST take the form X.Y.Z where X, Y, and Z are
non-negative integers...". See: https://semver.org/#spec-item-2
Also, even though version strings like "2.0.0-rc.2" or "1.0.0-beta" are
valid strings in semantic versioning format, in TUF we never needed
to add letters for our specification number.
That's why I validate that: spec_version is a . separated string
and when split it has a length of 3 and that each of the
three elements is a number.
The modules under the tuf/api folder in TUF are an alternative TUF
implementation. That's why they should use their own constant for
SPECIFICATION_VERSION in tuf/metadata/api.
This time, I used a list for the SPECIFICATION_VERSION constant in order
to retrieve major and minor versions easier.
I use the SPECIFICATION_VERSION to check that the given spec_version is
supported against the tuf code spec version.
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
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>
Clearing the OrderedDict makes it easier to see what happens and
avoids having to call OrderedDict() again.
Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
In issue #1418 in this comment:
https://github.com/theupdateframework/tuf/issues/1418#issuecomment-852147689
I summarized the discussion we had with the participants in this issue.
In summary: no additional changes are needed for "version" validation
considering there is "bump_version()" function for that.
If we won't be adding "version" validation elsewhere we can keep it
the way it is.
Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
Dict ordering is part of regular Dict from Python 3.7: Use OrderedDict
for signatures to make sure signatures are serialized in a reproducible
order even on 3.6.
The added benefit is that reader will immediately understand that the
order has some significance.
The actual type annotations are a bit convoluted because:
* typing does not include OrderedDict before 3.7 so can't use that
* Annotating inner types does not work for collections.OrderedDict
in older pythons (so have to use the "stringified annotations")
Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
store signatures in a Dict of keyid to Signature. This ensures
signature uniqueness. Raise in from_dict() if input contains multiple
different signatures for a keyid.
This changes Metadata object API, and makes it slightly different from
the file format: this is justified by making the API safer to use and
easier to validate.
Signed-off-by: Jussi Kukkonen <jkukkonen@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>
This change is relevant to the new metadata class Targets.
In the specification, when describing the Targets metadata file format
and more precisely "TARGETPATH" (or targets containing the actual
target files) it's said:
"It is allowed to have a TARGETS object with no TARGETPATH elements.
This can be used to indicate that no target files are available."
If there is no "TARGETPATH" keys for the dictionary "targets", this
would mean that "Targets.targets" is {}.
Make sure we test for that.
See: https://theupdateframework.github.io/specification/latest/#targetpath
Signed-off-by: Martin Vrachev <mvrachev@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>
pylint on the legacy code is by far the slowest part of linting (to
the extent that parallelizing the tox env itself doesn't really help):
pylint can fortunately parallelize itself.
Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
This is an initial setup: By default check only tuf/api/,
and ignore securesystemslib imports.
Change lint working directory to source root: This saves repeating a lot
of {toxinidir} in the command lines.
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>
We have tests which make sure we can use `Timestamp.update()` and
`Snapshot.update()` with MetaFile instance storing only version
(because length and hashes are optional).
Those tests were created to make sure that we are actually supporting
optional hashes and length when we call `update` for those classes, but
after we changed the `update()` signature to accept `MetaFile` instance
the tests are obsolete.
The reason is that length and hashes can be optional because of the
MetaFile implementation, no the update function itself and we have
other tests validating creating a MetaFie instance without hashes and
length.
Signed-off-by: Martin Vrachev <mvrachev@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>