-
Notifications
You must be signed in to change notification settings - Fork 375
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
rpmKeyring: Support keys with the same key ID #3398
Conversation
For now the test case only has two keys with matching key IDs. Will add tests with 3 or 4 keys with the same id at some later point. |
d64ab89
to
f48524f
Compare
rpmio/rpmkeyring.cc
Outdated
|
||
auto range = keyring->keys.equal_range(key->keyid); | ||
auto item = range.first; | ||
for (; item != range.second; item++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When at all possible, keep the loop variable inside the for. It should also be a reference, this is now making copies of the keys as it loops, and favor pre-increment. And then the whole thing becomes something like
for (auto const & item = range.first; item != range.second; ++item) {
if (item->second->fp == key->fp)
return rpmPubkeyLink(item->second);
}
return NULL;
Alternatively, leave the rkey declaration before the loop, just initialized to NULL and break out as needed, that's kinda matter of style.
rpmio/rpmkeyring.cc
Outdated
/* use first verifying key or last one with matching keyid */ | ||
auto range = keyring->keys.equal_range(keyid); | ||
auto item = range.first; | ||
for (; item != range.second; item++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, make item a reference inside the for-loop, pre-increment.
rpmio/rpmkeyring.cc
Outdated
|
||
if (key) | ||
pgpkey = key->pgpkey; | ||
if (pgpVerifySignature(pgpkey, sig, ctx) == RPMRC_OK) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem right, we're now verifying the same signature twice. It's expensive enough that we don't want to be doing that.
rpmio/rpmkeyring.cc
Outdated
if (range.first != range.second && !key) { | ||
for (item = range.first; item != range.second; item++) { | ||
key = item->second; | ||
rc = pubkeyVerifySig(key, sig, ctx); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't reverify, just collect any errors on the first run and then log them all if the result is a failure.
rpmKeyringModify still has |
Being outside loop is not a crime, it's just something to generally avoid if possible. On that note, ignore my earlier mumble about references and copies (as you did) - the thing you get from equal_range() is an iterator, not the item itself. "item" is not the best of names for it in all these cases. "it" is the c++ iterator counterpart for "int i" idiom. rpmKeyringVerifySig2() is unnecessarily convoluted though. Just push all the lints into the vector and on non-ok final result log it all in a loop, and use a c++ string so you don't need to manually clear it at the end. The no-key sanity check should only ever occur if we didn't find a key at all, not on any failure, and should use the same exact return code handling as the keyed version - you'll want a small helper function to handle the lints collection and all. |
rpmio/rpmkeyring.cc
Outdated
if (lints) { | ||
if (rc != RPMRC_OK) { | ||
/* Only failing keys */ | ||
for (auto item : results) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh and here we get to the wonders of C++ diversity 😆 - this syntax makes copies, so except for trivialities like integers you always want to use a reference in this construct, called "range based for loop". And, whenever possible, use a const reference at that.
So: for (auto const & item)
(and here "item" is appropriate because it is a reference to the item, not an iterator)
9bef89e
to
02842c1
Compare
OK, simplified the code. Just print out the right messages. I'd guess there shouldn't be a message for RPMRC_OK but the code for now does not make that assumption. |
Okay, looks better, but there are a couple of further improvements to be made. This being security sensitive code, you'll want the logic to be as lean and mean and obvious as you possibly can. There are two entirely separate cases here:
Add that helper function around the pgpVerifySignature2() and lints collection as I suggested in the previous comment, it's not that it's a large amount of code but to prove a point: even the simple three lines differ here, the first one using a gcc-extension syntax. Plus it'll make the business logic stand out more by isolating the C strings management out of sight entirely. The log output logic can probably simplified a lot by realizing that if we get an OK, we can just wipe out any previous results away: with the above logic impelemented, they can only be from non-matching keys and uninteresting if we found a match. So just do results.clear() if you got OK and then you can simply log any non-empty output. |
rpmio/rpmkeyring.cc
Outdated
|
||
for (auto const & item : results) { | ||
if (((rc == RPMRC_OK) == (item.first == RPMRC_OK)) | ||
&& item.second != "") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
item.second.empty() would be the more appropriate test for an empty C++ string. Not that it affects the results here.
rpmio/rpmkeyring.cc
Outdated
|
||
if (keyring && sig) { | ||
if (sig && ctx) { | ||
char *lints = NULL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will be moot once you add the wrapper around pgpVerifySignature2() which makes lints local, but this is another example of a variable in the wrong scope and in this case, dangerous: lints can be free'd and then accessed again later, but the pointer is not NULL'ed which is akin to leaving a booby trap in the code. Does pgpVerifySignature2() always assign to the lints pointer when passed? Maybe it does, but if not then we can end up accessing freed memory. You don't want to rely on such a thing.
rpmio/rpmkeyring.cc
Outdated
rpmPubkey key = NULL; | ||
std::vector<std::pair<int, std::string>> results = {}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And a scope-issue here too - the results vector is only needed inside the main if, so that's where it belongs. And key inside the only code that can actually end with it being set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Variables being in the most local scope is something that really belongs to self-review part, FWIW.
rpmio/rpmkeyring.cc
Outdated
*keyptr = pubkeyPrimarykey(key); | ||
|
||
for (auto const & item : results) { | ||
if (((rc == RPMRC_OK) == (item.first == RPMRC_OK)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a side-remark as I think clearing the results on OK is a better solution here, but such code can usually be made far more readable by giving the complex condition a human understandable name. Eg this might be something like
bool relevant_result = ((rc == RPMRC_OK) == (item.first == RPMRC_OK));
if (relevant_result && !item.second.empty)
rpmlog()...
This is wrong IMHO. There may be keys that get dropped in the loop because we can figure out they do not match. If we can get the fingerprint of some signatures at some point we might be able to filter out non-matching keys even more. In these cases we still need to run the NOKEY part if all keys got dropped. Also even in the NOKEY part we want to write NULL to the keyptr so callers can rely on their key variable getting updated. |
Oh, right. I missed the implications of the newly added filtering in the loop and that of course changes the landscape a lot, so taking that part back. As for the keyptr setting - yup, for NOKEY you'll want it and the key set to an explicit NULL, and similarly explicit for the case where we actually use a key value in there. It's not at all obvious what the value of "key" is when it falls out of the loops and all above it. |
Okay, seeing the latest version: I'd add an explicit "key = NULL" to the "No key" case, and then we can call this done. |
...and then you don't need to NULL it in the loop. |
Loop over the candidates during signature verification and use the one verifying it - iff any. Otherwise print error messages from all candidates. Resolves: rpm-software-management#3334
Guess it is a matter of taste. The original code just made sure the value of key was valid all the time - NULL or not. Now that we rely on the results vector that is not needed as urgently |
Matter of taste to a degree, sure, but we completely rely on the results vector for the nokey case anyhow so to me it's clearer that all the nokey logic is then in one spot. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks fine to me now.
Loop over the candidates during signature verification and use the one verifying it - iff any. Otherwise use last key with matching key ID (basically a random one).
Resolves: #3334