-
Notifications
You must be signed in to change notification settings - Fork 155
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
SSHClientConnectionOptions incorrectly verifies the passphrase of the wrong key #641
Comments
I think the problem you are running into here is that keys are loaded when you do the initial assignment to "co". As you said, this can cause a problem if you don't know the proper passphrase to use yet. It will try the alternate key after trying the default, but it never gets that far when you use You can avoid this problem by not creating your own SSHClientConnectionOptions. Instead, just do this in one step: async with asyncssh.connect(sys.argv[1], ignore_encrypted=False, passphrase=passphrase) as conn: Note that this only fails when you include the In general, trying to mix the config file settings with Options settings is a bit dangerous and may not always do what you want. It's better to keep all your options in either the arguments to |
OK, not sure about this. When I tried it like this: co = asyncssh.SSHClientConnectionOptions(passphrase=passphrase)
async with asyncssh.connect(sys.argv[1], options=co) as conn: it raised async with asyncssh.connect(sys.argv[1], ignore_encrypted=False, passphrase=passphrase) as conn: I didn't realize I could do this because I initially missed the very last sentence of the docs at https://asyncssh.readthedocs.io/en/latest/api.html#connect, that is:
Perhaps an additional note would be useful, explaining this difference between passing the passphrase via a separate
I'm guessing you meant Overall I'm happy with your solution, I don't really want to create a separate Many thanks for the very quick response! |
You're right that i meant The assumption here is that you should know whether your keys need a passphrase, and only provide one when they do. If you provide a passphrase and get it wrong, or if you explicitly specify a non-default location to an encrypted key without providing a passphrase, AsyncSSH will raise an error. Rather than prompting the user to enter a passphrase for a specific key from within AsyncSSH, have you considered using ssh-agent and something like ssh-askpass? That's designed to provide ways to prompt the user out of band for a passphrase when one is needed. |
Ahh, yes, you're absolutely right, I've missed this bit from the docs. Thanks for clarifying! I can leave out
Right, unfortunately in my case, I don't know that. I'm leaving this for the user to configure via
Same story, I'd rather leave it for the user to choose whether they want to use ssh-agent or provide the passphrase directly in my app. I myself don't particularly like ssh-agent, as I prefer the keys to be wiped from memory as soon as possible. Also not sure how well it'd work i.e. in Termux. |
So, to reiterate the actual issue: I'm quite happy with the way |
I've been thinking about this last point. I think maybe the reason you saw this was because there was no host information available when it tried to resolve the set of keys to use when you constructed the options object, before passing that to This kind of mismatch in the options can happen with any kind of conditional configuration, but generally the developer would never see this, since they don't really get to see what the intermediate values are when building partial options objects. So, I'm not sure if this is really worth documenting this edge case. It might take a lot of explanation that wouldn't be relevant to most people. Perhaps the better thing to document here is that a passphrase callback may be called each time an options object is constructed, based on what keys the caller has selected to load, even though some of those keys might not be encrypted or might not be used later for the actual authentication. |
Yes, it was precisely for this reason. Hostname to connect to is not specified in But I wouldn't expect the
Yes, something along those lines would be good, I think. To let the programmer know that this is a smart object that will attempt to load default keys, and that they might be the wrong keys if there's conditional host configuration present later. |
The original version of this options support didn't support an SSH config file, and so there was no notion of conditional values. The idea was to provide feedback as early as possible to the developer when they specified config options, and to do any computation or validation only once for any given options object you created, potentially reusing this collection of config settings for many connections over time without redoing all the work to reload them on every connect. In particular, loading keys can be quite expensive, and avoiding having to reload the keys each time can provide a substantial performance improvement. Now that the config file has to be re-evaluated every time, some of this benefit is lost, and it can also lead to issues like you saw here when one of the config file options depends on a value which is changing between requests. However, callers can still get the benefit here for whatever options they choose to pass in as keyword args instead of config file entries.
I'll see if I can come up with a note in the documentation to make this clearer. In the meantime, I've added an async construct() method you can use now in place of using the constructor directly when creating your own SSHClientConnectionOptions and SSHServerConnectionOptions objects. Also, I've added support for both callables and coroutines to be usable as a |
Assuming we have two client keys:
~/.ssh/id_ed25519
(encrypted with, say,passphrase1
) and~/.ssh/another_keyfile
(encrypted withpassphrase2
), and the following piece of~/.ssh/config
:and we try to connect to
example
using this sample client program:then the
asyncssh.SSHClientConnectionOptions
will incorrectly check the passphrase of~/.ssh/id_ed25519
and raiseKeyEncryptionError: Incorrect passphrase
. It shouldn't check the passphrase yet, as it doesn't even know which key will be used.If we remove that line, and don't pass
options=co
to.connect
, then the connection will work fine, using the right key as specified in the config.It appears there is no way to specify a passphrase to a non-standard key file.
The text was updated successfully, but these errors were encountered: