https://mjg59.dreamwidth.org/69646.html
Account name: [ ] Password [ ] [Log in]
(OpenID?) (Forgot it?) [ ] Remember Me
You're viewing [personal profile] mjg59's journal
Create a Dreamwidth Account Learn More
[ ] [Interest ] [Go]
Reload page in style: site light
Matthew Garrett
SSH agent extensions as an arbitrary RPC mechanism
SSH agent extensions as an arbitrary RPC mechanism
Jun. 11th, 2024 07:13 pm
[personal profile] mjg59
A while back, I wrote about using the SSH agent protocol to satisfy
WebAuthn requests. The main problem with this approach is that it
required starting the SSH agent with a special argument and also
involved being a little too friendly with the implementation - things
worked because I could provide an arbitrary public key and the
implementation never validated that, but it would be legitimate for
it to start doing so and then break everything. And it also only
worked for keys stored on tokens that ssh supports - there was no way
to extend this to other keystores on the client (such as the Secure
Enclave on Macs, or TPM-backed keys on PCs). I wanted a better
solution.
It turns out that it was far easier than I expected. The ssh agent
protocol is documented here, and the interesting part is the
extension support extension mechanism. Basically, you can declare an
extension and then just tunnel whatever you want over it. As before,
my goto was the go ssh agent package which conveniently implements
both the client and server side of this. Implementing the local agent
is trivial - look up SSH_AUTH_SOCK, connect to it, create a new agent
client that can communicate with that by calling NewClient, and then
implement the ExtendedAgent interface, create a new socket, and call
ServeAgent against that. Most of the ExtendedAgent functions should
simply call through to the original agent, with the exception of
Extension(). Just add a case statement against extensionType, define
some reasonably namespaced extension, and you're done.
Now you need to use this agent. You probably don't want to use this
for arbitrary hosts (agent forwarding should only be enabled for
remote systems you trust, not arbitrary machines you connect to - if
you enabled agent forwarding for github and github got compromised,
github would be able to use any private keys loaded into your agent,
and you probably don't want that). So the right approach is to add a
Host entry to the ssh config with a ForwardAgent stanza pointing at
the socket you created in your new agent. This way the configured
subset of remote hosts will automatically talk to this new custom
agent, while forwarding for anything else will still be at the user's
discretion.
For the remote end things are even easier. Look up SSH_AUTH_SOCK and
call NewClient as before, and then simply call client.Extension().
Whatever you stick in the contents argument will simply end up being
received at the client end. You now have a communication channel
between a the remote system and the local client, and what you do
with that is up to you. I'm using it to allow a remote system to
obtain auth tokens from Okta and forward WebAuthn challenges that can
either be satisfied via a local WebAuthn token or by passing the
query off to Mac TouchID, but there's fundamentally no constraints
whatsoever on what can be done here.
(If you want to do this on Windows and still have everything work
with existing clients you'll need to take this into account - Windows
didn't really do Unix sockets until recently so everything there is
awful)
Tags:
* advogato,
* fedora
* Previous Entry
* Add Memory
* Share This Entry
* Next Entry
---------------------------------------------------------------------
* Reply
---------------------------------------------------------------------
Profile
Matthew Garrett
About Matthew
Power management, mobile and firmware developer on Linux. Security
developer at Aurora. Ex-biologist. [personal profile] mjg59 on
Twitter. Content here should not be interpreted as the opinion of my
employer. Also on Mastodon.
Expand Cut Tags
No cut tags
Top of page