Why use SSH Certificates?
The default use for SSH is with a username/password combo. Why relying purely on
username/password is a poor idea is self evident, as passwords are commonly reused/stolen, so the next step up is using SSH keys so that an
attacker needs to have also acquired the SSH private key, as well as the
password used to protect the key pair. SSH keys can easily be produced using
OpenSSH:
$ ssh-keygen -b 384 -t ecdsa -f id_ec384
The common procedure is then to send your public key off to your favorite
IT-admin, who adds it to a list of known keys. If you're an organization of 8000
employees all working from home, your IT-admin is going to be sad and depressed
indeed.
Without going into details in the complete setup (that's a separate blog post,
and can be found plenty elsewhere), what your IT-admin can do instead is set
up a CA. From there they'll set up some form of RA, and what you instead can
do is send in your public key, have it signed and receive a certificate back.
This certificate is now your SSH passcode - the difference being that the
IT-admin never had to in any way interface directly with your public key. As
long as the signature is valid and the certificate hasn't expired, you're good
to go.
The Specification
The Specification
The specification for an RSA certificate is as follows:
78: string "ssh-rsa-cert-v01@openssh.com" 79: string nonce 80: mpint e 81: mpint n 82: uint64 serial 83: uint32 type 84: string key id 85: string valid principals 86: uint64 valid after 87: uint64 valid before 88: string critical options 89: string extensions 90: string reserved 91: string signature key 92: string signature
SSH certificates have no similarities to x509, as x509 was by the designers
deemed to be overly complex for the purpose. The SSH certificate format also
provides some nice perks that x509 does not. So, let's get into it.
Breaking it down
A sample SSH certificate looks like this.
ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAg5XCeFvniKnNbRZaclB83kTyF8zITCde4uRrv4mqesn4AAAADAQABAAABAQDiFqTBdKOWPeBeP1PiKSVy8ilfNChu5/6Z3iXu3Rdtg5ozu98IoAl4MtlklDdUDzvFkB+VPD/9gqHPKK8fTOhqgUPGoiCeZP3Ktr6NR53xd1QPQDeBvOMiYkPqQXziCQiVyL1WFzN616szrxsJ1Ni7WCHXcMTKOMruLv4es8FfB03wGDbBKVzMwo0JuZCicGg2pg/o8n9BPlzW6CvjUkmvUO3ycGKibPPFkiDgyuYynIbkMcmdShhY3XSOGB4UPeA3U4OiH6Z+09K9LqogWIcjxJeK0tObORd9QnQ4k1ba+/bbEfnFIQwLyzqXXPsPQ0Ud9upxVHD1lezRNE1DAIr9AAAAAAAAAAAAAAABAAAABWVqYmNhAAAAFAAAAAZlamJjYTAAAAAGZWpiY2ExAAAAAF7Q0HgAAAAAYLCytwAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQC8ajEtOvKxSPYSRL+A1y7Ye0baYHaR65KkzaT3U5XugHKHusvVPsDlRfSl598TsMjPQhbJt0O1SefMvXCbqdj776PWok5I1ScnLKJWRKzreeslEJZdcKTOUoT9Y5sg/LxC3xXwhIz+yLm8SbvQ7yQvPMmmlg5ldwccC8/0cua/25Vrjm0JhRjgxny65s2bNkClXXLmtevhvlQ7rXMQhpGmg5th156Ny/BUac7CQPEnDkRkhfsH8zKuh0NX19Y/93bwLsI7z+zP7CJJ11C0CpZl5yi2/8vqZUkufjRu/TH78EnLdCE/bkKcn0yyahG5BTh9dInrSclgCSiEPOYojvzjAAABFAAAAAxyc2Etc2hhMi0yNTYAAAEAj4/d9pKGGReo7B1ZbVBtHD9ftBfa5fjyPnuM8qbEMtYWgbx/MlCqVf2CL6VfJe2lvsg4ZZWvHyq0XBucFQqVHMekHJ71CymAs5/boGF4efLo56Ck6FF7tqM4dYmcO0aWRRQt3DyC24bLUZ4ZcdkyAgEm4EbTj9nPyvzbR57VsP/p+6WlszrGAHPwBlZ6my0g3cwPJSxwdV0USfMUIWooMIXLS7ocVZ7a8y+HF6qC2FGYIXYSgJuBaG5jlfXOojfKwA4tzwdy3ZziHxW50WDRkPrw6ZnXeRantJfMOhJ7ol9NSt1WcdkiAHqzDbEUpOz9UF0aSwxtIQYI7ONgl29a7w== Mike's Certificate
We can see right away a nice perk, the certificate begins by telling us
what's going to be inside, so off the ball we can figure out how we're going
to decode it. The astute of you will notice that the main body of the
certificate (between the prefix declaring the type and the comment at the
end) looks a lot like Base64, which it very correctly is. If you've seen a
certificate or two you'll also notice that every certificate of the same
type starts with the same text, and that's because the first characters are
(as stated in the specification above) the certificate type, in this case
ssh-rsa-cert-v01@openssh.com. So, throw the certificate body into
your favorite Base64 decoder and let's move on.
The byte array follows a simple structure: first a four byte integer declaring
the length of the structure, then the contents itself:
What you actually find inside the contents is up to the specification above,
hence the importance of knowing beforehand what you're parsing. There can also
be additional byte arrays of this format hidden within the payload, as we'll
see in a moment. Parsing our way through the byte array, we're going to
encounter the following objects, in this exact order:
Item | Description |
nonce |
A 16 or 32 bit random byte array generated on the CA, simply to
make collision attacks less likely. |
e | The RSA exponent of the public key for this certificate. Note that EC keys will have the entire key in its own byte structure instead of having its values directly in the certificate. |
n | The RSA modulus of the public key for this certificate. |
serial | 64 bit unsigned long with a serial number - up to the CA to determine the scheme |
type | 32 bit unsigned integer containing a 1 (for user certificates) or a 2 (host certificates) |
key id | A string filled in by the CA to identify the holder (end entity in x509 terms) owning the certificate. |
valid principals | Our first internal byte structure. The list of principals contains the accounts on the host machine which may be used with this certificate. An empty list will allow any user to make use of this certificate. The structure will look as follows: |
valid after | A 64 bit unsigned long containing the lower bounds of the certificate's validity. |
valid before | A 64 bit unsigned long containing the upper bounds of the certificate's validity. |
critical options | These will only be relevant for user certificates (as there are no official options for host certificates) and may be force-command (to have the client execute a certain command on authentication to the host) and/or source-address in order to bind the client to a single IP. This is a key-value pair, so adds another layer of complexity to the byte structure |
extensions |
The equivalent of x509 key usages, tells SSH what this
certificate is authorized to do. There are extensions pre-defined in
the specification, but it's up to you if you want to add your own as
well. The structure is an array of strings much like
principals: |
reserved |
A blank string, declared but not used at the moment. |
signature key |
This is where it starts getting fun. OpenSSH allows the
signature key to be of any OpenSSH usable format regardless of the key
being signed, so there is for example no reason why a RSA CA can't
sign a set of EC keys. The signature key is going to be parsed as a
byte structure and then parsed up into its component. The structure of
the key is defined in
RFC 4253, and for an RSA key would be as follows: string "ssh-rsa"
The nice part is that the first thing the key does is declare itself,
so you know exactly how to parse the rest of the structure.
|
signature |
Last but not least, the last byte array you're going to read is the signature. This is going to be again an embedded byte structure, starting with a string declaring the signature algorithm, and after that the actual signature, the data for which is all of the preceding fields including the signing key. |
And that's pretty much it! Happy hacking, and I hope I've managed to make somebody's day a bit better.
Cheers,
Mike Agrenius Kushner
Product Owner, EJBCA