Allow the certificate check to be ignored - #4042
Conversation
|
It looks like you've misunderstood the purpose of the Some systems (my primary motivation are binidngs) might want to always set a certificate check callback for simplicity in their setup but if they themselves have users which have not set the certificate check, returning this lets them and the library behave as though it wasn't set. What this code instead does is ignore the determination from the library about the validity of the certificate which is something we don't want to do in the library. If someone wants to ignore the certificate, we want them to return the all-ok return code. |
|
I was actually worried that it would allow such easy circumventing of certificate checks 😆 . I understand the motivation behind, but I'm not sure how to model that. IMHO the test I wrote was correct, in that the bindings |
|
The simplest way I think would be to store the previous error and then return that if the callback returns should be almost enough. The main point is that if we would have returned |
|
Should we allow the user to return anything he wants ? I think the http & ssh backend differ already, so I'm considering supporting only the following cases :
Opinions ? |
For anything non-zero, and not If I'm a user, and I return Related: #2716 |
|
Thanks for the cross-ref, that clears it up. IMHO, I don't think it's sane because I don't think there's a way to discern a |
There is, that's what My callbacks want to be able to round trip error codes; if my callback runs out of memory, I want to return Otherwise, I would have to set some flag in my callback, This is obviously a contrived example, but I think that it illustrates that error handling is simplified if you don't try to coalesce all callback returns to a single error code. This doesn't mean that anybody has to use these error codes, and generally they shouldn't, but it allows them to use them when it simplifies their lives. |
|
It seems I might not understand the relation between My (original) proposal would be to do have internals calls to client code handled like this : so the client can do Another way would be to make a new Sorry, I have a knack for nice error handling 😉. |
You probably do, I wrote
There shouldn't be any, because you shouldn't be returning your own error codes. You need to return libgit2 error codes. If an odb backend returns Custom backends do this today and these semantics are already well agreed upon. Your example of converting everything to
Yes, this is exactly what you need to do. These error values are completely meaningless to libgit2 and you shouldn't return them to the library. You need to return a libgit2 error code that the library understand. If you're writing a custom backend (be it transport, odb, refdb, etc) and you need to communicate with it, then you should set a flag in the backend that your library consumer can interrogate later. That's what This looks like what you're trying to do with |
|
At least I'm green on the error class vs error code nuance 😉 .
That's new to me, and I don't think it's clearly documented anywhere ? So callbacks are expected to use
It is, as a way of preserving user error codes without conflating them with our own. So a user can know whether it's a Using the custom backends as an example, apart from the redis one, none of them use Again, I'm just thinking out loud. If you think it's out of scope, fine (at least the current rules are now clear to me, even though IMHO they're ambiguous). |
979be48 to
2b35ecd
Compare
|
Rebased, now that the security fixes are in. I had shuffled all cert-related tests around (I should have included that, sorry). I hope I got the tests right, because they pass 😉. |
| { | ||
| GIT_UNUSED(cert); | ||
|
|
||
| struct cert_options *options = payload; |
There was a problem hiding this comment.
The windows builds fail because this isn't declared at the top of the block.
There was a problem hiding this comment.
Oops, will fix ASAP !
|
It looks like the test doesn't quite work for WinHTTP. There we seem to be going all the way through to requesting the repository and thus getting a 404 instead of our own error. |
|
Should be good now. The failure comes from a HTTP 502 from bitbucket. |
d5ea513 to
77ed35c
Compare
|
Rebased. I don't think the failures are related, though I might be wrong. |
|
Failures are unrelated, yes. They are due to our recently broken master branch when our bundled zlib is used, which I've fixed a few days ago. The other errors are due to the usual segfaults with poxyproxy... sigh Time to finally get our test suite polished up again. |
77ed35c to
dc2fb1d
Compare
| g_cert_options.return_code = GIT_EPEEL; | ||
|
|
||
| cl_git_fail_with(GIT_EPEEL, | ||
| git_clone(&g_repo, "https://wrong.host.badssl.com/fake.git", "./fake", &g_options)); |
There was a problem hiding this comment.
GIT_EPEEL is a very confusing error to use here. GIT_EUSER would be much better as it's the one we have set up for people to return and have it passed all the way back.
There was a problem hiding this comment.
Ok, will fix. The intent was to make sure we preserved whatever the users' callback replied to us (hence the non-sensical GIT_EPEEL).
| g_cert_options.return_code = GIT_PASSTHROUGH; | ||
|
|
||
| cl_git_fail_with(GIT_ERROR, | ||
| git_clone(&g_repo, "https://wrong.host.badssl.com/fake.git", "./fake", &g_options)); |
There was a problem hiding this comment.
This should be returning GIT_ECERTIFICATE as the passthrough means we want to use the result that the library was going to use if we didn't have the callback.
I believe this GIT_ERROR/-1 actually comes from there not actually being a git repository at the given location.
There was a problem hiding this comment.
I'll have to recheck what's actually going on w.r.t to the first part. The second part is correct, we're actually succeeding the cert check, but the clone machinery obviously fails down the line, returning GIT_ERROR.
| git_clone(&g_repo, "https://expired.badssl.com/fake.git", "./fake", NULL)); | ||
|
|
||
| cl_git_fail_with(GIT_ECERTIFICATE, | ||
| git_clone(&g_repo, "https://expired.badssl.com/fake.git", "./fake", &opts)); |
There was a problem hiding this comment.
This isn't testing anything other than us bubbling up the return code, does it? We've already tested this elsewhere.
There was a problem hiding this comment.
I'm merging all cert-related test cases in this one, so the "elsewhere" is this one now, thus it's testing that the TLS layer doesn't accept invalid certificates by default.
There was a problem hiding this comment.
I'm merging all cert-related test cases in this one,
And I wish you hadn't done that, because I'm now reading the badssl test cases as though they're testing how we handle the feature you're writing instead of what they're for.
| cl_git_fail_with(GIT_ECERTIFICATE, | ||
| git_clone(&g_repo, "https://wrong.host.badssl.com/fake.git", "./fake", NULL)); | ||
| cl_git_fail_with(GIT_ECERTIFICATE, | ||
| git_clone(&g_repo, "https://wrong.host.badssl.com/fake.git", "./fake", &opts)); |
There was a problem hiding this comment.
It's the same here as above. Adding the callback here doesn't add anything to any of these tests which are about making sure we do reject a bunch of common certificate errors.
There was a problem hiding this comment.
Adding the callback is there to check we don't have a difference in behavior w.r.t invalid certificates whether there's a callback or not. I can remove if you feel like it's too much though.
| git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./fake", &g_options)); | ||
| } | ||
|
|
||
| void test_online_cert__certificate_invalid_ssh(void) |
There was a problem hiding this comment.
This test name (and __certificate_invalid above) is misleading. These certificates are valid, but we're testing here the ability to abort a clone in the certificate callback.
There was a problem hiding this comment.
Agreed, would __valid_certificate_user_invalidated be somewhat clearer that we respect the users' decision ?
cf87b14 to
546f4bd
Compare
|
Rebased, with a focus on only passthrough (so the test cases aren't merged anymore) and squashed everything into one commit, with some more testing on top (and comments). I'm surprised how confusing this code can become when looking at it too much 😫. I have been tempted to move that whole certificate check thing into a helper function somewhere, for DRY reasons, but didn't find a satisfying location. |
546f4bd to
8be2da2
Compare
8be2da2 to
a4ac903
Compare
| } | ||
|
|
||
| giterr_clear(); | ||
| giterr_state_capture(&git_error_state, GIT_ERROR); |
There was a problem hiding this comment.
Shouldn't this be giterr_state_capture(&error_state, GIT_ERROR);?
66943e4 to
a16670e
Compare
|
I don't understand the linking failure MSVC reports. I've tried both using the GIT_EXTERN macro and not marking those function as extern, and it doesn't want to link, even though those same function are used in For reference, function-not-found error, link error. @ethomson Any insight on what might be wrong ? |
pks-t
left a comment
There was a problem hiding this comment.
I'm surprised this could've passed on Travis
| if (error < 0 && !giterr_last()) | ||
| if (error == GIT_PASSTHROUGH) { | ||
| /* Nothing could have failed previously, return success */ | ||
| git_error_restore(&error_state); |
There was a problem hiding this comment.
This has to be giterr_state_restore
| } else if (error < 0 && !giterr_last()) { | ||
| giterr_set(GITERR_NET, "user cancelled certificate check"); | ||
| } | ||
| git_error_free(&error_state); |
|
🙄 Good grief, thanks @pks-t. Travis doesn't care about WinHTTP so that explains it. |
a16670e to
6bf7a9b
Compare
6a1c87e to
99acd3f
Compare
Now transports just need to call `git_transport_smart_certificate_check`, and the user's GIT_PASSTHROUGH reply will be handled for them.
99acd3f to
0735656
Compare
|
|
||
| return t->certificate_check_cb(cert, valid, hostname, t->message_cb_payload); | ||
| return error; | ||
| } |
There was a problem hiding this comment.
All this code shouldn't go here. This function is a way for code external to the library to execute the certificate check callback, The error you want to insert here may not exist in the code implementing a transport. They may be catching an exception several languages apart, and there isn't going to be any error state to capture.
If we need common code inside the library to do this repeated code, that needs its own function.
There was a problem hiding this comment.
I really wanted to make that passthrough-dance generic across all transports, and having it here removes the need for an external transport implementation to handle this case on their side. I can easily move it in a private function, but IMHO that kinda defeat the purpose, since it will no longer be "externally usable". I also like the eat-your-own-dogfood aspect of it.
The usage pattern I had in mind was that the external transport would "check the certificate" while connecting (whatever that means), call this with cert_error set to GIT_OK for valid, GIT_ECERTIFICATE for invalid (that's the "non-existing error case" from your point above, which our SSH transport demonstrates), anything else for spurious unrelated error, and this function would automatically preserve the callback error (which might not even be aware that an external transport is in use) or restore the transport's original success/failure state on a passthrough.
I'm not sure I understand your point about exceptions being several languages apart : should it be safe for an higher-level (say Ruby) external transport to raise an exception and catch that around the (say git_remote_download) call that started the activity, without going through our code at all 😨 ?
As an API sidenote, I was actually wondering about the usage pattern of those functions : the external code might end up calling a NULL function pointer, and I don't think there's a way for external users to know they're about to SEGV (git_transport_smart_credentials shares the same problem).
|
This has been tackled as part of #4879. |
Fixes #3440