SSH Key Authentication for Ruby
This projects aims to provide authentication (signing and verifying) by using
ssh keys.
Why?
Most infrastructures I’ve managed have had bits of single-sign-on mixed
not-so-single-sign-on. Further, the first thing I disable in sshd is always
password authentication and make sure everyone gets an ssh key and knows
how to use it.
I like ssh keys. They’re simple. On my infrastructure, they’re everywhere. It’s
how you log in. Further, like kerberos tickets, you can forward your ssh agent
along with your ssh sessions letting you forward your credentials more
naturally than, say, with ssl or pgp.
With tools like mcollective, openvpn, etc, you often need a way to authorize
and authenticate users. With OpenVPN you can use passwords or SSL certs.
Managing my own SSL CA is not my idea of a good time, but is often required
for some things. Further, teaching engineers how to use SSH with keys is hard
enough without saying “oh, here’s another authentication scheme with your SSL
cert.”
Add PGP to the mix, and you’ve got big frownyface – from me, anyway. So many
systems require one unique authentication mechanism that you end up with N
mechanisms. Hopefully this tool lets you avoid that if possible.
The original point of writing this module was to allow authentication of
messages from specific users over mcollective. The goal is to be able to use
keys in your ssh-agent and sign messages and have them verified on the remote
end by using your own user’s authorized_keys file.
Get with the downloading
To install sshkeyauth, you can use gem:
gem install sshkeyauth
Otherwise, you are welcome to clone this repository.
Example
First Requirements
Signing requires you either have an ssh agent running or explicitly tell the
signer about an ssh private key file.
Sign a string
Signing a string will sign with all known ssh keys. This is useful for
broadcast media like mcollective where you don’t have the luxury of
challenge-response (two-way, multi-message communication). For systems with
challenge-response, you can simply iterate over the results and try each
signature.
require "ssh/key/signer" signer = SSH::Key::Signer.new # By default we'll automatically use your ssh agent for signing keys. # But if you want to also (or instead) specify a private key file, use this: signer.add_key_file("/path/to/key", "mypassphrase") # Of course, if your key doesn't have a passphrase, you can omit the argument: # This works well with host keys, too, if you want to sign messages as a host # instead of a user. signer.add_key_file("/path/to/key") # Now sign a string (will sign with all known private or agent keys) signatures = signer.sign("Hello world") # 'signatures' is an array of SSH::Key::Signature which has properties: # type - the ssh key type (ssh-rsa, ssh-dsa) # signature - the signature string (bytes) # identity - the identity that signed the original string
Verifying a signature
Verifying requires you have a signature and the original string.
The verifier will use your ssh agent if possible. Disable this by setting
Verifier#use_agent=false. It will also try to find your user’s authorized_keys
file and the public keys in it. Additionally, you can add public key strings
with Verifier#add_public_key_data.
require "ssh/key/verifier" verifier = SSH::Key::Verifier.new # Again, by default we'll try to use your ssh agent. # from above, 'signatures' verified = verifier.verify?(signatures, original) puts "Verified: #{verified}" # the above should print 'true' if verification was successful.
You could also use SSH::Key::Verifier#verify(…) to get a detailed result of
the verification.
Using the stuff in samples/
The samples/ directory has a ‘client’ and ‘server’. The client does signing;
server does verification. Client writes to stdout; server reads from stdin.
They both use json for and base64 for simplicity in message passing.
Check your ssh-agent has keys loaded:
% ssh-add -l 2048 4b:cc:f1:b7:60:99:ac:77:b3:51:38:3e:f4:b6:d6:74 id_rsa_tester (RSA)
Client-only:
% ruby client.rb "hello" {"signature":"INBpScDBdmmRjrEbLjyarGwoZh1tGEtVVHX8syq94Z/hN36B0r88FhCXjcPj\ngafhVDhZXAaoVSSE0L2o8i045F7Fbn8Uh0jjmCWKJX8jY0ZqNVWfetmfjbEL\nuovuTR5+pBhnf5QMVgAXirNBqvT0vOPcrOuHFr9kcAH7RYdLydPyQmVDyjGa\nOTOffumaUFLX/KbCM/4jR0zpVA8i4E9MlEwd7gGNy1RmE4chZvexP6rgMMyk\n52BUT12QIiXiXbY7SzR5AwrfSJbw+CAudQEHm4rjPTgATZvqhyeNuEuYjpwA\n3RTRl9gA7Qrc/Gcwt9jMlkKgmOr8OMQRPZr2l2YMHg==\n","original":"hello"}
Both:
% ruby client.rb “hello” | SSH_AUTH_SOCK= ruby server.rb
W, [2010-10-10T03:07:02.866074 #26915] WARN — : SSH Agent not available
true
We empty ‘SSH_AUTH_SOCK’ to prevent the ‘server’ from using the ssh agent for
public key knowledge (forces authorized_keys usage). Server will try all ssh
keys in your user’s authorized_keys file.
Scaling/Speed
You can use ‘openssl speed rsa’ to benchmark how many signatures and
verifications per second you can do. A sample output looks like this:
sign verify sign/s verify/s rsa 512 bits 0.000143s 0.000013s 6985.0 79548.6 rsa 1024 bits 0.000645s 0.000035s 1550.7 28378.6 rsa 2048 bits 0.004012s 0.000113s 249.3 8858.3 rsa 4096 bits 0.026481s 0.000406s 37.8 2460.7
Verification is much faster than signing, which is good becuase in many cases
you probably have many more public keys to verify against than you would to
sign against.
TODO
- Currently we iterate all known public keys (for a user, etc), this may be
undesirable in some cases. - Send the public key along with any signature so the reciever can easily look
up which ssh key should be used to verify. That is, we should look for matching
public keys that are valid for the user and then verify with that. the full
public key to identify which public key we are using to avoid unnecessary extra key
signature checking, which can be costly at scale (especially if you are verifying
against a known_hosts file of thousands and can’t find the host entry). - Currently restricted to signing with private keys and verifying with public keys.
Obviously, signing with public keys and verifying with private keys would be useful,
too. However, I don’t think we can verify using private keys using ssh agents. - Add helper methods for verifying a signature against a known_hosts key
(lookup using ssh-keygen -F )