Skip to content
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
Cannot retrieve contributors at this time

OpenSSH Principals

OpenSSH is incorporated into many commercial products, but very few of those companies assist OpenSSH with funding.

Table of contents:

OpenSSH daemon, in its default configuration, is more than sufficient for one user to remote login into one machine, with sophisticated security.

A typical fresh install has SSH host keys, and the OpenSSH daemon started on first boot.

The machine's public host key is extracted on first use:

laptop$ ssh 2001:0db8::1
The authenticity of host '2001:0db8::1 (2001:0db8::1)' can't be established.

As another option:

laptop$ ssh-keyscan -t ed25519 2001:0db8::1

At this moment, the public host key to be added in ${HOME}/.ssh/known_hosts (by the SSH protocol) is validated, completing the trust on first use (TOFU) security model.

Are you sure you want to continue connecting (yes/no)? yes

The identified public host key's fingerprint will be added to the DNS zone of the name associated with the host's IP address:	IN	AAAA	2001:0db8::1

Print the Secure Shell (Key) Fingerprint (SSHFP) Resource Record (RR) for the public host key:

laptop$ ssh-keygen -r 2001:0db8::1 -f
laptop$ ssh-keyscan -D -t ed25519 2001:0db8::1

SSHFP RR are added to the DNS zone for the chosen name, and signed:	IN	SSHFP	4 1 ec3fdef6b0bsd2dc3f8163a1e08069c92ff96d49	IN	SSHFP	4 2 f510394a7e4490800bsd15d066ecd0ae131ec6b03c7ca269a9013a62730ab2ca

After the DNS zone propagates, SSH fingerprints can be verified by a DNSSEC aware resolver:

laptop$ ssh -o "VerifyHostKeyDNS ask"

In addition, the configuration option "VisualHostKey yes" generates "random art" on every login, for comparison:

laptop$ ssh -o "VisualHostKey yes"
|  +  ....+o.     |
|.o *.   o +.     |
|=.B.   o oo.     |
|+X.=    o..o     |
|BoO .   S.  .    |
|XX o             |
|B+B              |
|+o               |
|oE               |

The operating system installation script may include the system administrator's public key, for OpenSSH "publickey" authentication (on first boot):

Next authentication method: publickey

Otherwise, she must login using the interractive "password" authentication method, to add her public key on the host:

laptop$ ssh "cat - >> .ssh/authorized_keys" < "${HOME}/.ssh/"

i.e. Remote access to local user "puffy" on host "" is now under control.

As more systems and users are added to the mix, it becomes cumbersome to manage and keep remote access secure solely with the TOFU model.

To scale, OpenSSH implements a mechanism for using a trusted third party in the form of Certificate Authority (CA).


While the concept is similar to X.509 (commonly used in TLS), OpenSSH certificates are a different format. They contain much less information, which doesn't mean they are less useful. In fact, OpenSSH certificates allow fine-grained control to local users and hosts with security principal.

OpenSSH provides both identification and authentication of hosts and users, which are distinct entities, as seen earlier with TOFU. Certificates expand this security model.

Hosts and users still need to go through TOFU, but only once, which greatly improves security and usability of OpenSSH keys, and a fortiori the TOFU model itself.

Moreoever, instead of configuring access on each host, principal permissions are set inside the certificate, and signed by a central authority.

In computer security, and in OpenSSL, principals can be hosts, users, or groups of various types.

Each principal has a distinct security identifier (SID):

  • Host identities can be part of individual hostname principals, or groups.
  • User identities can be part of individual user principals, or groups.

Expanding on the previous scenario, let's introduce a CA and combine these characteristics by adding a second host.

To make it tangible, we'll focus on the email service of a simple network, and our two hosts in this example will become the primary and backup MX.

Certificate Authority

OpenSSH Certificate Authorities may be created on encrypted flash memory card/drive, and operated on single purpose secure machines, with no network access.

 | |-ca/
 | | |-.ssh/
 | | | |-ssh_ca_ed25519
 | | | |
 | | |-ssh_ca.krl
 | | |-host/
 | | | |
 | | | | |-.ssh/
 | | | | | |
 | | | | | |
 | | | |
 | | | | |-.ssh/
 | | | | | |
 | | | | | |
 | | |-user/
 | | | |-puffy/
 | | | | |-.ssh/
 | | | | | |
 | | | | | |
 | | |-auth/
 | | | |-all
 | | | |-backup
 | | | |-database
 | | | |-dns
 | | | |-email
 | | | |-router
 | | | |-nas
 | | | |-web

Since two hosts are part of the same service, they'll be part of the same "email" group principal. In this way, authentication is based on service type, instead on each host in the service group.

n.b. The files under /etc/ssh/ca/auth represent other services on the network, and are informational cues.

Let's create each part of this sample CA, starting with its layout:

airgap# mkdir -pm 755 /etc/ssh/ca/.ssh

Generate a new password protected CA key:

airgap# ssh-keygen -t ed25519 -C -f /etc/ssh/ca/.ssh/ssh_ca_ed25519

Transfer the public CA key from CA to sysadmin's laptop, and further to the host:

laptop$ scp /etc/ssh/ca/.ssh/ \

The CA is now ready to issue signed certificates with the following information:

  • public key
  • (-I) identity
  • (-n) principal(s)
  • (-V) validity interval
  • (-z) serial number
  • (-O) configuration options

Host certificates

OpenSSH host certificates authenticate server hosts to users.

Add a new host to CA:

airgap# mkdir -pm 755 /etc/ssh/ca/host/

Add the identified public host key:

airgap# cp /etc/ssh/ca/host/

Generate a (-h) host certificate, using the hostname as identity and principal:

airgap# ssh-keygen -s /etc/ssh/ca/.ssh/ssh_ca_ed25519 \
	-h \
	-I \
	-n \
	-V always:forever \
	-z 1 \


airgap# ssh-keygen -L \
	-f /etc/ssh/ca/host/

	Type: host certificate
	Public key: ED25519-CERT SHA256:OBsd...
	Signing CA: ED25519 SHA256:Zszg...
	Key ID: ""
	Serial: 1
	Valid: forever
	Critical Options: (none)
	Extensions: (none)

Transfer the host certificate from CA to sysadmin's laptop, and further to the host:

laptop$ scp /etc/ssh/ca/host/ \

The second host, "" is added to the CA and processed as above.

User certificates

OpenSSH user certificates authenticate users to servers.

On her end, the user generates a new password protected key:

laptop$ ssh-keygen -t ed25519 -C
> Enter file in which to save the key (/home/puffy/.ssh/id_ed25519):

The user sends her public key to her system administrator, to generate a user certificate, using "email" group principal:

airgap# ssh-keygen -s /etc/ssh/ca/.ssh/ssh_ca_ed25519 \
	-I puffy \
	-n email \
        -O no-x11-forwarding \
	-V +31d \
	-z 2 \
> Signed user key /etc/ssh/ca/user/puffy/.ssh/ id "puffy" serial 2 for puffy valid from 2018-10-02T13:41:00 to 2018-10-31T13:42:37


airgap# ssh-keygen -L \
	-f /etc/ssh/ca/user/puffy/.ssh/

	Type: user certificate
	Public key: ED25519-CERT SHA256:0B5d...
	Signing CA: ED25519 SHA256:Zszg...
	Key ID: "puffy"
	Serial: 2
	Valid: from 2018-10-02T13:41:00 to 2018-10-31T13:42:37
	Critical Options: (none)

Transfer the user certificate from CA to sysadmin's laptop (and further to the user):

laptop$ scp /etc/ssh/ca/user/puffy/.ssh/ \

Key Revocation List

Key revocation lists (KRL) avoid the process of recreating the CA on each change.

Create a new KRL

airgap# ssh-keygen -k \
	-f /etc/ssh/ca/ssh_ca.krl \
	-s /etc/ssh/ca/.ssh/ \
	-z 2 \

Update an existing KRL

airgap# ssh-keygen -k \
	-f /etc/ssh/ca/ssh_ca.krl \
	-u \
	-s /etc/ssh/ca/.ssh/ \
	-z 2 \

Query KRL

airgap# ssh-keygen -Q \
	-f /etc/ssh/ca/ssh_ca.krl \

On each update, transfer the KRL to a distribution location for all hosts, or:

laptop$ scp /etc/ssh/ca/ssh_ca.krl
laptop$ scp /etc/ssh/ca/ssh_ca.krl

By now, hosts and users are in possession of their certificates, and OpenSSH needs to be aware of this.

Daemon configuration

 | |-ca/
 | | |-ssh_ca.krl
 | |-principals/
 | | |-puffy
 | |-ssh_host_ed25519_key
 | |
 | |

On hosts, we associate principals with local users:

mercury# mkdir -m 755 /etc/ssh/principals
mercury# echo -e 'email' > /etc/ssh/principals/puffy

i.e. The "email" group principal has access to local user "puffy", on hosts "" and "".

Relevant daemon configuration snippet:

mercury# cat /etc/ssh/sshd_config
#HostKey /etc/ssh/ssh_host_ed25519_key

HostCertificate /etc/ssh/

# Certificate Authority
TrustedUserCAKeys /etc/ssh/ca/.ssh/
RevokedKeys /etc/ssh/ca/ssh_ca.krl
CASignatureAlgorithms  ssh-ed25519

PermitRootLogin no # default: prohibit-password

#PubkeyAuthentication yes

AuthorizedKeysFile	none # default: .ssh/authorized_keys

AuthorizedPrincipalsFile /etc/ssh/principals/%u # default: none

PasswordAuthentication no # default: yes

Client configuration

 | |-config
 | |-known_hosts
 | |-id_ed25519
 | |
 | |

Relevant client configuration snippet for CA public key:

laptop$ cat ~/.ssh/known_hosts
@cert-authority * ssh-ed25519 AAAA...

Hash "known_hosts" file:

laptop$ ssh-keygen -H \
	-f ~/.ssh/known_hosts

Relevant client configuration snippet for SSHFP:

laptop$ cat ~/.ssh/config
Host *
	VerifyHostKeyDNS ask
	VisualHostKey yes


OpenSSH configuration is flexible, and it is well worth upgrading to certificates, even for a single user.

It is possible to further centralize with OpenSSH gateway, using relayd and rdomain:

gateway$ cat /etc/ssh/sshd_config

ListenAddress :: rdomain 1

RDomain %D

Contributions welcome, please open a Pull Request