Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Metadata API: Implement threshold verification #1436

Merged
merged 4 commits into from
Jul 12, 2021

Conversation

jku
Copy link
Member

@jku jku commented Jun 4, 2021

#1417 is merged so this is good to go

The delegating Metadata (root or targets) verifies that the delegated metadata is signed by required threshold of keys for the delegated role.

This implementation raises UnsignedMetadataError if threshold is not met. Other possible exceptions are programming errors.

Fixes #1306

Copy link
Contributor

@sechkova sechkova left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I've become biased after looking at the code in the next-gen client for a while because I couldn't point out any issues :)

I was searching for a way to avoid adding role_name as an argument of verify_delegate but I saw you already opened #1425 about it so +1 from me on that.

Copy link
Collaborator

@MVrachev MVrachev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't found any issues either.
LGTM!

tests/test_api.py Show resolved Hide resolved
try:
key.verify_signature(delegate, signed_serializer)
# keyids are unique. Try to make sure the public keys are too
signing_keys.add(key.keyval["public"])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe making sure that we have keyval["public"] uniqueness can be done instead in Root and Targets?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah possible but I'd say it's a separate issue: #1429

Copy link
Member Author

@jku jku Jun 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only very recently realised the TUF spec does not require the existence of "public" at all -- the three keys defined in the spec all happen to have it but it isn't required.

So this line needs to change. The spec does require that KEYIDs are unique so verify_delegate() will just respect that and will not try to poke into the internals.

tests/test_api.py Outdated Show resolved Hide resolved
tuf/api/metadata.py Outdated Show resolved Hide resolved
@jku jku force-pushed the verify-delegate branch 2 times, most recently from 3e6def1 to 3185a30 Compare June 11, 2021 18:27
keys = self.signed.delegations.keys
# Assume role names are unique in delegations.roles: #1426
roles = self.signed.delegations.roles
role = next((r for r in roles if r.name == role_name), None)
Copy link
Member Author

@jku jku Jun 11, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also from trishank: "What does this line do? Looks like magic"
My response was:

it returns the first role in delegations with name role_name (or None if no match).

I agree it's not pretty and a bit too clever.
I could do something like this instead:

role = None
for delegated_role in roles:
    if delegated_role.name == role_name:
        role = delegated_role
        break

but that's 5 lines of initialize-then-modify antipattern: not the end of the world but not perfect either.

the real fix is probably #1426 though (make roles a OrderedDict). Then this code becomes readable and efficient:
role = roles.get(role_name)

I think I'd leave it as is ... but if others think that's too clever I'm happy to change to the more verbose one

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps add a one-liner comment for less familiar readers?

Find the first role in delegations with name role_name (or None if no match)

@jku jku marked this pull request as ready for review June 11, 2021 18:39
@MVrachev
Copy link
Collaborator

In our discussion with @jku in issue #1438 we agreed that we should make sure that the metadata is accepted even if it has a couple of invalid keys, but it accommodates the requirement placed by threshold.
I think I don't see a test for that. Do you plan to add one @jku?

@jku
Copy link
Member Author

jku commented Jun 16, 2021

In our discussion with @jku in issue #1438 we agreed that we should make sure that the metadata is accepted even if it has a couple of invalid keys, but it accommodates the requirement placed by threshold.
I think I don't see a test for that. Do you plan to add one @jku?

I think there are two things that we should definitely test:

  • test that Key.verify_signature() raises UnsignedMetadataError (or potentially a derived error) if keys are in any way invalid. This should happen in Handle exceptions in verify #1435, I've added the tests that I think make sense there
  • test that verify_delegate() handles UnsignedMetadataErrors coming from Key.verify_signature(): catching that error is fine as long as threshold is eventually reached: this should happen in this PR. It does look like there is no test case with failing signatures that should eventually succeed (because threshold is still reached). That would make sense to add (but it does not need to be about invalid keys in particular).

Soo, yes I guess I plan to add one now...

@jku
Copy link
Member Author

jku commented Jun 16, 2021

rebased on develop, added a commit to add a tests for

  • signature that fails to verify, but overall signatures reach threshold
  • threshold that actually contains multiple signatures

This also changes one error type as suggested by teodora

Copy link
Collaborator

@MVrachev MVrachev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding the tests!
Seems good to me!

@jku
Copy link
Member Author

jku commented Jun 22, 2021

Added a commit that removes the attempt to enforce key contents uniqueness:

  • I only very recently realised the TUF spec does not require the existence of "public" key within the Key.keyval dictionary at all -- the three keys defined in the spec all happen to have it but it isn't required.
  • We know that the attempt is not protection against maliciously crafted keys anyway: creating multiple representations of the same key is not difficult

Luckily the spec requires that KEYIDs are unique so verify_delegate() will just respect that and will not try to poke into the internals of the keys.

Copy link
Member

@joshuagl joshuagl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me. I wonder if the parameter names in verify_delegate could be renamed to more clearly indicate that role_name and delegate are related (an identifier and an instance)?

tuf/api/metadata.py Outdated Show resolved Hide resolved
keys = self.signed.delegations.keys
# Assume role names are unique in delegations.roles: #1426
roles = self.signed.delegations.roles
role = next((r for r in roles if r.name == role_name), None)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps add a one-liner comment for less familiar readers?

Find the first role in delegations with name role_name (or None if no match)

@jku
Copy link
Member Author

jku commented Jul 5, 2021

I chose the more verbose names: it's easy to lose track of where the keys and thresholds really come from and what they are applied on so more verbose seems reasonable.

Jussi Kukkonen added 3 commits July 5, 2021 15:13
The delegating Metadata (root or targets) verifies that the delegated
metadata is signed by required threshold of keys for the delegated
role.

Calling the function on non-delegator-metadata or giving a rolename
that is not actually delegated by the delegator is considered a
programming error and ValueError is raised.

If the threshold is not reached, UnsignedMetadataError is raised.

Tweak type annotation of Delegations.keys to match the one for
Root.keys (so they can be assigned to same local variable).

Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
Make sure verify_delegate() succeeds when threshold is reached even if
some signatures fail to verify.

Make sure higher threshold (2/2) works.

Change error type for "Call is valid only on delegator metadata" error.

Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
There was an attempt at ensuring key content uniqueness in
verify_delegate() by making sure the values corresponding to "public"
keys in Key.keyval dictionaries are unique. This had two issues:
 * it wasn't a security measure: it's not difficult to produce two
   different "public" values of the same key content
 * Spec does not actually guarantee the existence of "public" key in
   the keyval dictionary (the three keys included in the spec just all
   happen to have it)

Luckily the spec does require KEYIDs to be unique so we do not need to
do all this: Just count keyids of keys with verified signatures. Keep
building a Set of keyids as a belt-and-suspenders strategy: Role keyids
are currently guaranteed to be unique but we'd notice here if they
weren't.

Add a logger call for failed verifys: this might useful to figure out
which keys exactly are the issue when a delegate can not be verified.

Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
@jku
Copy link
Member Author

jku commented Jul 5, 2021

Someone made mypy rules stricter in the mean time 😬 so had to rebase and fix the annotation (no changes in existing commits, only in two new commit).

@joshuagl joshuagl self-requested a review July 7, 2021 08:40
Copy link
Member

@joshuagl joshuagl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like how easy this is to follow, nice work.

tuf/api/metadata.py Outdated Show resolved Hide resolved
tuf/api/metadata.py Outdated Show resolved Hide resolved
tuf/api/metadata.py Outdated Show resolved Hide resolved
MVrachev added a commit to MVrachev/tuf that referenced this pull request Jul 8, 2021
We don't neeed those tests given that verify_with_threshold is a
placeholder until theupdateframework#1436
is merged.

Signed-off-by: Martin Vrachev <mvrachev@vmware.com>
* Rename arguments so connection between the role name and the
  metadata is stronger.
* Also add a comment on the list comprehension + next() trick.
* Add return value annotation
* Raise early if delegations is None to make the flow more obvious
  (and modify test case so we have coverage for the new case)

Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
@jku
Copy link
Member Author

jku commented Jul 11, 2021

Yes, agree with all suggestions. I've combined all the improvements from suggestions into one commit.

The changes since last push are

  • use if self.signed.delegations is None to make that path clearer as suggested
  • Add a test for this specific case (this required modifying the test data)
  • tweak comments as suggested

It's minor enough that I don't think this absolutely needs another approval (so I will merge with the one I have) but ... there's not a lot of code that's more critical for TUF so feel free to have another look

Copy link
Collaborator

@MVrachev MVrachev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at the new changes and it seems good to me!

Copy link
Member

@joshuagl joshuagl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@joshuagl joshuagl merged commit bd5912b into theupdateframework:develop Jul 12, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement verification by a threshold of keys
4 participants