Generating and Using OpenSSH User Certificates

Lee Painter

Creating OpenSSH Certificates is now possible from the Maverick Legacy API via the SshCertificateAuthority helper class. This article is a quick reference for users that understand what OpenSSH certificates are and already understand how to use these with the standard OpenSSH client and server.

A cheat sheet is available in our blog which outlines the basic configuration steps required to use and generate SSH certificates using the OpenSSH command-line clients.


Creating a Certificate Authority

There is nothing special about an SSH certificate authority, it's just a plain old SSH private/public key pair. We create one like any other SSH key.

SshKeyPair caKey = SshKeyPairGenerator.generateKeyPair(SshKeyPairGenerator.ED25519);

This should be stored like any other private key

SshKeyUtils.savePrivateKey(caKey, "secret", "My Certificate Authority", new File("caKey"));

This will result in two files stored in the current working directory caKey and caKey.pub. This is your certificate authority private key and should be kept secure. 

It's general practice to use separate CA for users and host keys. However, in this article, I will simply refer to this CA key in future examples. 


Generating User Certificates

SSH Certificates still require that we generate a public/private key for the end-user. So we generate one as usual with the SshKeyPairGenerator.

SshKeyPair userKey = SshKeyPairGenerator.generateKeyPair(SshKeyPairGenerator.ED25519);

Before saving the key, we can generate an SSH Certificate for this key using the API. We can specify the serial number, the user's name, and the validity of the certificate (in days).

SshCertificate cert = SshCertificateAuthority.generateUserCertificate(userKey, 0L, "lee", 365, caKey);

We can now save the certificate to distribute to the user.

SshKeyUtils.saveCertificate(cert, "secret", "Lee's SSH key and Certificate", new File("userKey"));

This will result in three files stored in the current working directory userKey the private key, userKey.pub the public key, and userKey-cert.pub the certificate file. All these files should be distributed to the user.


Certificate With Extensions

Certificates support a number of default extensions, when you generate a certificate and do not specify any extensions (for example with the above code) a default set of extensions will be added to your certificates. These are: 

permit-X11-forwarding
permit-agent-forwarding
permit-port-forwarding
permit-pty
permit-user-rc
 

You can modify these by using CertificateExtension.Builder class. To create the default list of extensions you can 

List<CertificateExtension> exts = new CertificateExtension.Builder().defaultExtensions().build();

To add custom extensions, for example, GitHub uses login@github.com extension you can:

List<CertificateExtension> exts = new CertificateExtension.Builder()
                  .defaultExtensions()
                  .customStringExtension("login@github.com", "ludup")
                  .build();

If you want to modify the number of default extensions you can build the list like:

List<CertificateExtension> exts = new CertificateExtension.Builder()
.knownExtension(CertificateExtension.PERMIT_PTY)
.knownExtension(CertificateExtension.PERMIT_USER_RC)
                  .customStringExtension("login@github.com", "ludup")
                  .build();

Certificates also support a small number of critical options, like force-command to restrict the command a user can execute with the certificate. Or source-address limiting the source IP address to a specific list. 

Like extensions, critical options have a builder pattern so you can add these to your certificates when you generate them.

List<CriticalOption> opts = new CriticalOption.Builder()
.forceCommand("ssh admin@special-host.com")
.sourceAddress("192.168.1.0/24", "127.0.0.1")
.build();

These List objects can then be passed into SshCertificateAuthority.generateCertificate method with the appropriate arguments.


Using User Certificates

We can load the SshCertificate back with the following call:

SshCertificate cert = SshKeyUtils.getCertificate(new File("userKey"), "secret");

Note that we now have a SshCertificate object instead of a SshKeyPair. We can pass this to the API to authenticate the user as part of the PublicKeyAuthentication API.

ssh.authenticate(new PublicKeyAuthentication(cert));