Table of Contents

Certificates

An OpenSSH user certificate is a public key signed by a certificate authority (CA), with the principals (user names) it may log in as and a validity period. Servers that trust the CA (sshd's TrustedUserCAKeys) accept it without an authorized_keys entry, which is how many organizations hand out short-lived SSH access. Host certificates work the other way, letting clients trust servers by their CA.

When to use

  • Your organization issues SSH certificates (ssh-keygen -s, HashiCorp Vault's SSH secrets engine, step-ca, Teleport, cloud identity-aware proxies).
  • You want access that expires on its own instead of keys in authorized_keys.

Log in with a certificate

using var key = await SshPrivateKey.LoadAsync("id_ed25519", cancellationToken: cancellationToken);
var certificate = SshCertificate.Parse(await File.ReadAllTextAsync("id_ed25519-cert.pub", cancellationToken));

if (certificate.ValidBefore < DateTimeOffset.UtcNow.AddMinutes(5))
{
    throw new InvalidOperationException($"Certificate {certificate.KeyId} expires at {certificate.ValidBefore:u}; renew it.");
}

Console.WriteLine($"{certificate.KeyId}: principals {string.Join(", ", certificate.Principals)}, signed by {certificate.AuthorityFingerprint}");

await using var client = await SshClient.ConnectAsync("example.com", 22, new SshClientOptions
{
    UserName = "deploy",
    PrivateKeys = [key.WithCertificate(certificate), key],   // the certificate first, the plain key as a fallback
    HostKeyVerifier = verifier,
}, cancellationToken);

WithCertificate pairs the key with its certificate; the pair is offered first, and the plain key after it in case the server does not take the certificate.

A certificate for an agent key

// A short-lived certificate from your CA, for a key the agent holds.
var agent = SshAgent.FromEnvironment() ?? throw new InvalidOperationException("No SSH agent is running.");
var certificate = SshCertificate.Parse(await File.ReadAllTextAsync("id_ed25519-cert.pub", cancellationToken));
var agentKey = (await agent.GetKeysAsync(cancellationToken)).First(k => k.PublicKey.Span.SequenceEqual(certificate.PublicKey.Span));
var options = new SshClientOptions { UserName = "deploy", PrivateKeys = [agentKey.WithCertificate(certificate)], HostKeyVerifier = verifier };

From ssh_config

Connecting by alias finds certificates as ssh does: CertificateFile entries, and <identity>-cert.pub next to each identity file.

Trust hosts by their certificates

A host certificate lets clients trust every server the CA signed, without a known_hosts line per server. Add the CA to known_hosts:

@cert-authority *.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...

KnownHostsVerifier then accepts a host certificate from that CA for matching host names, within its validity period. To apply your own rules, see the custom verifier.

Notes and pitfalls

  • Principals. The server checks that the user name you log in as is one of the certificate's principals (certificate.Principals); a mismatch is an ordinary authentication failure.
  • Expiry. An expired certificate is refused by the server; check ValidBefore and renew in time, as above.
  • Critical options such as force-command or source-address are enforced by the server (certificate.CriticalOptions lists them).
  • Revocation. Servers revoke user certificates with RevokedKeys; on the client side, RevokedHostKeys can revoke host certificates by serial or key id.

See also