Usual use of SSH keys
The role of trust between client and server
SSH keys are powerful tools that notably allow to guarantee the identity of the remote peer on the network without exchanging any shared secret.
To connect to a server through SSH, the client needs to trust the identity of the server (to avoid attacks of the type man in the middle), and before granting access to the system, a server obviously needs to trust the identity of the connecting client to allow access to the server only to trusted user.
Most of the time, these two independent types of identity verification are based on SSH keys in the ways we will describe below before showing how SSH certificates can be used for the same operations.
Authentication of the client by the server
In order for the server to trust the connecting client, the system
administrator (or the users themself, if they can authenticate using other
means, e.g. a password) must place the public key of each user in the file
~/.ssh/authorized_keys
of the target account.
When the client connects to the server, it uses the private SSH key of the
user, whose authenticity is verified by the server using the matching public key
stored in the file ~/.ssh/authorized_keys
of the account the client tries to
connect to.
Authentication of the server by the client
On its first connection to a server, the client records a public key provided by the server, presuming the key really belongs to the host it seeks to connect to.
Before trusting it though, the client will usually ask the user to confirm whether they accepts the connection and the recording of the server key. This is presented this way:
~$ ssh lab.example.org
The authenticity of host 'lab.example.org (192.168.1.3)' can't be established.
ECDSA key fingerprint is SHA256:wxocsOPNEfYuuTcXGuitKR8RUowpGbgpXTEsXXirAFI.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'lab.example.org' (ECDSA) to the list of known hosts.
If the user accepts to continue connecting to the server, the client will
record the server key in its file ~/.ssh/known_hosts
(by default indexed on a
hash of the name of the server as provided to the SSH client).
The command ssh-keygen -F <hôte>
allows for searching a host key in the file
~/.ssh/known_hosts
. For example:
~$ ssh-keygen -F lab.example.org
# Host lab.example.org found: line 133
|1|4M/wUtH5ptcDaJ7BwKPcpci6iZA=|Ba7Gk24pQ4qdIg4hR5KVIjdGo7Y= ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJeB2B5fn2SX2m38FI2OeZN3Bvb7E9XIImWIQiCCdEEAH+0lbFhRxgTU2mAOlp3+Ciz9DgKW2yAX7g26E73LWSI=
On the next connections, the client will similarly look up the server public
key in the file ~/.ssh/known_hosts
. If the recorded public key matches the
private key used on the server, the client will trust the server and proceed to
the authentication. Should this not be the case, the client will end the
connection and display a message to the user indicating the server they tries
to connect to did not provide the same SSH key it did on the last connection,
which could indicate that the host is not really the server they tries to
connect to.
We notice here a weakness in the protocol, as on the first connection, the user who blindly accepts the SSH key of the server takes the risk to connect to a server that is not the one they thinks it is.
This risk can be avoided if the public keys of the servers are distributed by
different means for the users to add to their known_hosts
file, allowing for
authenticating the server on their very first connection.
A variant of this solution if made easier in recent-ish versions of OpenSSH
where, as shown in the example above, instead of answering yes
or no
to the
client asking whether to continue the connection, it is possible to provide the
fingerprint of the expected SSH key. The connection will then only continue if
the fingerprint really matches the one of the key provided by the server (which
is more reliable than visually verifying the fingerprint printed by the
client).
Use of SSH based on SSH certificates
As we just saw above, in the “usual” use of SSH keys:
- the server authenticates clients using SSH keys stored in the user account's
file
~/.ssh/authorized_keys
; - the client authenticates the server it connects to using the public key
provided by the server on its first connection and stored in the
known_hosts
file.
Using certificates, the server, just like the client, still use its pair of keys, but additionally use an certificate that consists of a signature of the associated SSH key made using a key that represents the Certification Authority, expanded with some identity information, principals, options.
Thus, instead of trusting public keys stored in authorized_keys
files, the
server only needs to trust a single public key representing the Certification
Authority that creates the certificates, that enables the verification of the
authenticity of the SSH keys.
Likewise, instead of having to keep a list of trusted public keys of servers
in its known_hosts
file, the client only needs to trust a public key
representing the Certification Authority that provides the SSH certificates.
Practical implementation of certificates
Two distinct certification authorities
In practice, the pair of keys that represents the certification authority that generates the certificates is a simple pair of SSH keys like those used for the authentication of servers or clients.
It is recommended to use a different pair of keys as certification authority for the servers and the clients. This distinction is not mandatory, but it is consistent with the authentication of servers and the one of clients being two independent operations, although using the same method.
Generation of certification authority keys
We first generate the two key pairs that will serve as certification authority
(CA), one for the server certificates, the other for the client certificates,
using the command ssh-keygen
, like we do for any SSH key.
These keys can be of any type, here we will use ed25519
keys (ecdsa
or
other types can also be used). The option -C
used below allows to clearly
differentiate the two public keys by providing a comment:
It is recommended to choose a long passphrase to correctly protect these particularly sensitive keys, and only use them on a properly isolated and secure host.
The option -a
used in the commands above is not strictly required but allows
to specify the number of KDF (key derivation function) rounds used by
ssh-keygen
to protect the private key using its passphrase.
When this option is not specified, by default 16 rounds are used. As stated in the ssh-keygen(1) manpage, “higher numbers result in slower passphrase verification and increased resistance to brute-force password cracking (should the keys be stolen).” The passphrase being verified only when the key is loaded and the verification not being significantly slowed down, it is recommended to increase this number.
Here is an example output of one of these commands:
# ssh-keygen -a 256 -t ed25519 -C 'server CA' -f server_ac
Generating public/private ed25519 key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in server_ac
Your public key has been saved in server_ac.pub
The key fingerprint is:
SHA256:e7wnSbMmyStx4K0+Mk3dj/03m/bI4Fk823EL0WdD8l4 server CA
The key's randomart image is:
+--[ED25519 256]--+
| |
| |
| . . |
| . = |
| . +S. . +E|
| + +o+ .o.+|
| o =.ooB..+o.|
| o = +.*o+=.B*|
| +.o.+.oo.*=*|
+----[SHA256]-----+
Creation of a client certificate
A certificate is generated for a client by signing the public key generated by
the user using the key of the certification authority (client_ac
in our
case):
In this command:
-s /path/to/client_ac
indicates the path to the private key that represents the certification authority used for client certificates;-I key_id
indicates an identity that can be freely chosen and can contain spaces (although accented characters will not be correctly printed). It will be logged on certificate use and can be used to designate the certificate in a key revocation list if it does not have a serial number (see my article “Key Revocation Lists on OpenSSH”);-n name1,name2,…
specifies one or more principals to be included in the certificate:- for a client certificate, at least one principal must be specified to designate the username of the account the user will be allowed to log in as (multiple usernames can be specified, separated by commas),
- if the username of the target account is not found in the certificate
principals, the authentication will fail and the error message “
error: Certificate invalid: name is not a listed principal
” will be found in the logs (by defaultauth.log
on Debian), - if no principal appear in the certificate, the authentication will fail and
the error message “
error: Certificate lacks principal list
” will be found in the logs (although this seems to contradict the manpagessh-keygen(1)
that states that “By default, generated certificates are valid for all users or hosts.”).
/path/to/user_key.pub
is the argument that designates the public key of the user that the certificate will certify.
Below is an example output of the generation of a certificate based on the
public key id_ed25519.pub
for a user (the passphrase asked below is the one
that protects the private key client_ac
):
# ssh-keygen -s client_ac -I "Herschel Krustofski" -n krusty id_ed25519.pub
Enter passphrase:
Signed user key id_ed25519-cert.pub: id "Herschel Krustofski" serial 0 valid forever
The certificate resulting from this operation will be written to the file
id_ed25519-cert.pub
in the same directory as the one of the key provided as
argument (the current directory in this example).
A certificate can be examined using the option -L
of ssh-keygen
:
$ ssh-keygen -L -f id_ed25519-cert.pub
id_ed25519-cert.pub:
Type: ssh-ed25519-cert-v01@openssh.com user certificate
Public key: ED25519-CERT SHA256:QfLKGkjku0mCMS9bTJPtuCkie3Cr158kb9C7axTYXRE
Signing CA: ED25519 SHA256:iuqKv2w9iXf+Cj3+0+/EqPxghEYAbWJQuReDkZXtBYs (using ssh-ed25519)
Key ID: "Herschel Krustofski"
Serial: 0
Valid: forever
Principals:
krusty
Critical Options: (none)
Extensions:
permit-X11-forwarding
permit-agent-forwarding
permit-port-forwarding
permit-pty
permit-user-rc
Use of a client certificate
On the SSH server side
In order for the SSH server to recognize the validity of the signatures in the
certificates presented by the connecting SSH clients, the public key of the
certification authority that generated them must be made available to it (in
our example, it is the key stored in the file client_ac.pub
).
This key must be provided in a file referenced in the configuration file of the
SSH server (/etc/ssh/sshd_config
on Debian GNU/Linux systems) using the
directive TrustedUserCAKeys
:
TrustedUserCAKeys /etc/ssh/client_ac_keys
This file may contain several keys used to generate client certificates. We will append the key of our example to the file:
The SSH server must then be restarted to take into account the new configuration:
A certificate presented by a client will by default only be considered when its
lists of principals (provided by the -n
option on its generation)
contains the username of the account it tries to connect to.
It is however possible to accept, for each account, a list of principals different from its username.
For that, a file specific to each account must be specified that will be read
to find the list of principals accepted for the account. These files must be
referred to by the directive AuthorizedPrincipalsFile
in sshd_config
.
The argument to this directive can include %u
to designate the login of the
account, %U
for the uid of the account, and %h
for its homedir. For
example, to use a file for each user in the directory
/etc/ssh/auth_principals
, we could use:
AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u
Instead of using a file, it is also possible to dynamically generate a list
of accepted principals for an account using a custom command specified by the
directive AuthorizedPrincipalsCommand
that will be run by the account
specified by AuthorizedPrincipalsCommandUser
. For more details, refer to the
description of these directives in the manpage
sshd_config(5).
On the client side
On the client side, the user must send their SSH public key, id_ed25519.pub
in our example, to the server administrator and they will receive in return the
certificate id_ed25519-cert.pub
generated from it and signed by the client
certification authority.
The user simply has to place the file id_ed25519-cert.pub
in the same
directory as the file id_ed25519
of their SSH key. During a connection that
uses this id_ed25519
key, the SSH client will then automatically present the
certificate to the server.
We can also note that our local SSH agent automatically loads the certificate when loading the key:
~$ ssh-add -l
The agent has no identities.
~$ ssh-add ~/.ssh/id_ed25519
Enter passphrase for /home/herschel/.ssh/id_ed25519:
Identity added: /home/herschel/.ssh/id_ed25519 (herschel@laptop)
Certificate added: /home/herschel/.ssh/id_ed25519-cert.pub (Herschel Krustofski)
~$ ssh-add -l
256 SHA256:QfLKGkjku0mCMS9bTJPtuCkie3Cr158kb9C7axTYXRE herschel@laptop (ED25519)
256 SHA256:QfLKGkjku0mCMS9bTJPtuCkie3Cr158kb9C7axTYXRE herschel@laptop (ED25519-CERT)
The user therefore does not need to change their configuration compared to the usual use of their SSH key. They can mention the key as usual:
- using the
-i
option forssh
on the command line, for example: - or using the directive
IdentityFile
in their client configuration file~/.ssh/config
(usually in aHost
section), for example :IdentityFile ~/.ssh/id_ed25519
Assuming the file ~/.ssh/authorized_keys
of the target account on the server
we connect to using the SSH key is empty, we notice that the server still
authorizes the connection based on the SSH key thanks to the certificate:
$ ssh -i ~/.ssh/id_ed25519 lab.exemple.org
Linux lab 5.10.0-22-amd64 #1 SMP Debian 5.10.178-3 (2023-04-22) x86_64
Last login: Thu May 11 11:05:23 2023 from 192.168.1.42
~$
The use of the certificate can also be verified using the option -v
of the
ssh
client during the connection:
$ ssh -v -i ~/.ssh/id_ed25519 lab.exemple.org
...
debug1: Will attempt key: /home/herschel/.ssh/id_ed25519 ED25519 SHA256:QfLKGkjku0mCMS9bTJPtuCkie3Cr158kb9C7axTYXRE explicit agent
debug1: Will attempt key: /home/herschel/.ssh/id_ed25519 ED25519-CERT SHA256:QfLKGkjku0mCMS9bTJPtuCkie3Cr158kb9C7axTYXRE explicit agent
...
debug1: Authentications that can continue: publickey,password
debug1: Next authentication method: publickey
debug1: Offering public key: /home/herschel/.ssh/id_ed25519 ED25519 SHA256:QfLKGkjku0mCMS9bTJPtuCkie3Cr158kb9C7axTYXRE explicit agent
debug1: Authentications that can continue: publickey,password
debug1: Offering public key: /home/herschel/.ssh/id_ed25519 ED25519-CERT SHA256:QfLKGkjku0mCMS9bTJPtuCkie3Cr158kb9C7axTYXRE explicit agent
debug1: Server accepts key: /home/herschel/.ssh/id_ed25519 ED25519-CERT SHA256:QfLKGkjku0mCMS9bTJPtuCkie3Cr158kb9C7axTYXRE explicit agent
debug1: Authentication succeeded (publickey).
...
Linux lab 5.10.0-22-amd64 #1 SMP Debian 5.10.178-3 (2023-04-22) x86_64
Last login: Thu May 11 11:05:23 2023 from 192.168.1.42
~$
We notice in these debugging lines printed by ssh
that the SSH key is first
provided to the server without the certificate. As it was not authorized in the
file ~/.ssh/authorized_keys
of the remote account, the authentication fails
and the client tries again by also presenting the certificate, which this time
leads to a successful authentication.
This shows that the usual use of the SSH keys based on authorized_keys
is
still possible while using a certificate and that it is attempted first by the
SSH client.
On the server side, in case of successful authentication, a message similar to the one below will be printed in the system logs:
Accepted publickey for herschel from 192.168.1.42 port 40160 ssh2: ED25519-CERT SHA256:QfLKGkjku0mCMS9bTJPtuCkie3Cr158kb9C7axTYXRE ID Herschel Krustofski (serial 0) CA ED25519 SHA256:iuqKv2w9iXf+Cj3+0+/EqPxghEYAbWJQuReDkZXtBYs
Creation of a server certificate
A certificate is generated for a server by signing one of its public keys using
the key of the certification authority (server_ac
in our example):
On this command:
-s /path/to/server_ac
indicates the path to the private key that represents the certification authority used for server certificates;-I key_id
indicates an identity that can be freely chosen and can contain spaces (although accented characters will not be correctly printed). It will be shown in the verbose output of the client (using the-v
option ofssh
) on certificate use and can be used to designate the certificate in a key revocation list if it does not have a serial number (see my article “Key Revocation Lists on OpenSSH”);-h
must be used to generate certificates for hosts/servers, as opposed to certificates authenticating clients. If this options is not present, the client will refuse the server certificate with the error “Certificate invalid: not a host certificate
”;-n principal1,principal2,…
specifies one or more principals to be included in the certificate. For a server certificate, these principals must be the (comma separated list of) DNS names of the server for which the certificate should be valid. If no principals are specified, SSH clients will accept the certificate from any server (as long as it originates from a trusted certification authority, obviously).
Below is an example output of the generation of a certificate based on the
public key ssh_host_ecdsa_key.pub
for a server lab.example.org
(the
passphrase asked below is the one that protects the private key server_ac
):
# ssh-keygen -s server_ac -I "Test Server" -h -n lab.example.org,w1.example.org ssh_host_ecdsa_key.pub
Enter passphrase:
Signed host key ssh_host_ecdsa_key-cert.pub: id "Test Server" serial 0 for lab.example.org valid forever
The certificate resulting from this operation will be written in the file
ssh_host_ecdsa_key-cert.pub
in the same directory as the one of the key
provided as argument (the current directory in this example).
Again, the certificate can be examined using the option -L
of ssh-keygen
:
# ssh-keygen -L -f ssh_host_ecdsa_key-cert.pub
ssh_host_ecdsa_key-cert.pub:
Type: ecdsa-sha2-nistp256-cert-v01@openssh.com host certificate
Public key: ECDSA-CERT SHA256:wxocsORNEfXuuTcXGwitKR8RUompGbgpXTEsXXirAFI
Signing CA: ED25519 SHA256:e7wnSbMmyStx4K0+Mk3dj/03m/bI4Fk823EL0WdD8l4 (using ssh-ed25519)
Key ID: "Test Server"
Serial: 0
Valid: forever
Principals:
lab.example.org
w1.example.org
Critical Options: (none)
Extensions: (none)
Use of a server certificate
On the SSH server side
The certificate thus generated must be referenced in the SSH server configuration
(in its sshd_config
file) using the directive HostCertificate
:
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
This certificate must match one of the keys designated explicitly or implicitly
(through its default values) by a HostKey
directive. In our example, the
certificate matches the key /etc/ssh/ssh_host_ed25519_key
used by default by
the SSH server.
As with each modification of its configuration, the SSH server must be restarted to take into account its new configuration:
The SSH server provides several host keys to connecting clients using different
algorithms or key types, and the client will choose the type it prefers to use
based on a list specified in its configuration (defined by the directive
HostKeyAlgorithms
).
Note however that, by default, this list considers certificates before any keys. This means that the selection of the certificate types by the client is independent of the selection of key types.
On the client side
While connecting, the client receives a certificate provided by the SSH server. It then needs the public key of the certification authority used to generate it to be able to verify the signature of the server public key (included in the certificate) by this authority.
The public key of the certification authority must therefore be specified to
the client. This is achieved in the SSH client configuration file
~/.ssh/known_hosts
.
This specification is realized using a line starting with @cert-authority
:
- a pattern or a list of patterns separated by commas must be given as the first parameter to designate the servers this CA key should be applied to;
- in these patterns,
*
matches a series of characters and?
any character; - the pattern ”
*
” used alone specifies that this key can sign certificates for any server; - the rest of the line provides the type of key and the key itself, encoded in base64.
Below are some examples specifying certification authority keys:
@cert-authority * ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICSsDZABRKynUuiV5kyfDEgmwEgZ8a1pCsuRficEksDU Some Certification Authority
@cert-authority *.sub.example.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICSsDZABRKynUuiV5kyfDEgmwEgZ8a1pCsuRficEksDU Another certification authority
@cert-authority *.example.com,*.example.net ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICSsDZABRKynUuiV5kyfDEgmwEgZ8a1pCsuRficEksDU CA for Acme, Corp.
Once the public key of the certification authority is specified, the client will be able to automatically authenticate servers that provide a certificate it has generated.
The use of the keyword @cert-authority
in a known_hosts
file is described
in the section SSH_KNOWN_HOSTS FILE FORMAT
of the manpage
sshd(8).
This section explains in particular that the SSH client compares the pattern
given as an argument to the keyword @cert-authority
with:
- the name of the server as it was provided by the user;
- or the name specified by the directive
HostKeyAlias
if the connection was configured in the file~/.ssh/ssh_config
and such a directive was used; - or else with the canonical name of the server if the directive
CanonicalizeHostname
was used in~/.ssh/ssh_config
.
The option -v
of the ssh
command allows to verify, if necessary, that the
certificate was used by the SSH client and gives useful information if this is
not the case. Here are a few examples of messages printed by ssh
in
different cases:
- in the positive case where the name of the server we're trying to connect to
really matches the pattern of a
@cert-authority
line, that the provided certificate includes the full name of the server in its principals (or that there are no principals), and that the key signature is valid, everything will work as intended ant the command will print the following messages (before continuing with its own authentication):~$ ssh -v lab.example.org OpenSSH_8.4p1 Debian-5+deb11u1, OpenSSL 1.1.1n 15 Mar 2022 ... debug1: Server host certificate: ecdsa-sha2-nistp256-cert-v01@openssh.com SHA256:wxocsORNEfXuuTcXGwitKR8RUompGbgpXTEsXXirAFI, serial 0 ID "Test Server" CA ssh-ed25519 SHA256:e7wnS bMmyStx4K0+Mk3dj/03m/bI4Fk823EL0WdD8l4 valid forever debug1: Host 'lab.example.org' is known and matches the ECDSA-CERT host certificate. debug1: Found CA key in /home/herschel/.ssh/known_hosts:1 ... [follow messages related to the user authentication]
- in case no pattern given with a keyword
@cert-authority
matches the name of the server, or the matching certificate was not generated by the configured CA key, the message “No matching CA found
” will be printed and the client will fall back to using the usual method of validation of the server public key and will look it up in theknown_host
file and offer the user to add it if it was not found:~$ ssh -v lab.example.org ... debug1: Server host certificate: ecdsa-sha2-nistp256-cert-v01@openssh.com SHA256:wxocsORNEfXuuTcXGwitKR8RUompGbgpXTEsXXirAFI, serial 0 ID "Test Server" CA ssh-ed25519 SHA256:e7wnS bMmyStx4K0+Mk3dj/03m/bI4Fk823EL0WdD8l4 valid forever debug1: No matching CA found. Retry with plain key The authenticity of host 'lab.example.org (192.168.1.3)' can't be established. ECDSA key fingerprint is SHA256:wxocsORNEfXuuTcXGwitKR8RUompGbgpXTEsXXirAFI. Are you sure you want to continue connecting (yes/no/[fingerprint])?
- lastly, if the principal of the certificate is
example.org
and we are trying to connect tolab.example.org
for example (or any other name not included in the list of principals), the client will print the message “Certificate invalid: name is not a listed principal
” (unless the list of principals is empty, in which case the client will accept any valid certificate):~$ ssh -v lab.example.org ... debug1: Server host certificate: ecdsa-sha2-nistp256-cert-v01@openssh.com SHA256:wxocsORNEfXuuTcXGwitKR8RUompGbgpXTEsXXirAFI, serial 0 ID "Test Server" CA ssh-ed25519 SHA256:e7wnS bMmyStx4K0+Mk3dj/03m/bI4Fk823EL0WdD8l4 valid forever debug1: Host 'lab.example.org' is known and matches the ECDSA-CERT host certificate. debug1: Found CA key in /home/herschel/.ssh/known_hosts:1 Certificate invalid: name is not a listed principal debug1: No matching CA found. Retry with plain key ...
Additional options supported by certificates
Serial number
We saw in the examples printing certificates (using ssh-keygen -L
) earlier
that other options were supported in these certificates, in addition to the
ones described in the previous sections.
Among these additional options, a serial number (designated by the label
Serial:
when printing certificates) can be specified when generating a
certificate, using the option -z
of the command ssh-keygen
. It allows to
distinguish a certificate from other certificates issued by the certification
authority (which can be used in particular to designate a certificate in the
Key Revocation Lists, ou KRLs, as described in my article “Key Revocation
Lists on OpenSSH”).
Validity interval
The validity (designated by the label Valid:
when printing certificate) can
be specified using the option -V
of ssh-keygen
. If the argument for this
option is a single time, then the certificate will be valid from now to the
specified time. If two times separated by a colon are provided, the
certificate will be valid in this time interval.
A time specification can be a date in the formats YYYYMMDD
or
YYYYMMDD-HHMM[SS]
, or a date relative to the present time, as described in
the section TIME FORMATS
of the manpage
sshd_config(5),
for example “-15d
” for 15 days in the past, or “+5w3d
” for 5 weeks and 3
days in the future.
The starting time can also be always
to indicate that the certificate does
not have a start of validity, and the ending time can be forever
to indicate
the certificate does not have any expiration date. Both these keywords
represent the default validity interval when none are provided.
Other options
More options can be specified using the command option -O option
, that allow
to limit the use of the certificate, depending on its use. Refer to section
CERTIFICATES
in
ssh-keygen(1)
for a more detailed description. They can be used to forbid transfers of ports,
agent, X11 displays, force a command, etc., like SSH key
options
can in the file ~/.ssh/authorized_keys
.