# SSH using Certificates

John Simpson `<jms1@jms1.net>` 2019-10-07

Last updated 2019-10-12

Figuring out how to set up and use an SSH CA.

# On CA system

I'm managing my SSH CA under a Keybase private folder.

### Create CA

```
$ cd /keybase/private/jms1/sshca
$ ssh-keygen \
    -t rsa \
    -b 4096 \
    -N '' \
    -C 'jms1.net SSH CA 2019-10-12' \
    -f ssh_ca
Generating public/private rsa key pair.
Your identification has been saved in ssh_ca.
Your public key has been saved in ssh_ca.pub.
The key fingerprint is:
SHA256:IuM5UmMtOhMmLXhhShzsqJZXVZNjPKEJOZSbvzlVP4Q jms1.net SSH CA 2019-10-12
The key's randomart image is:
+---[RSA 4096]----+
|..  .oo o+o      |
|...  +..o*.      |
|ooo   =o. o .    |
|+= . =     E .   |
|* * O + S . o    |
|.B B * o .   o   |
|. * +   +     .  |
|   + . +         |
|        .        |
+----[SHA256]-----+
```

Options:

* **`-t`** Key type. Can be `rsa`, `dsa`, `ecdsa`, or `ed25519`. Note that `dsa` should not be used because of security issues.

* **`-b`** Key bits.

* **`-N`** New passphrase (or in this case, no passphrase).

* **`-C`** Comment for public key.

* **`-f`** Filename. Note that this will create both private and public key files.

# For each host

### Sign the host keys to produce certificates

Copy the host's `/etc/ssh/ssh_host_xxx.pub` files to your local machine. Note that you don't *need* to copy the secret key files, although you may want to keep copies in case the machines need to be rebuilt.

```
cd /keybase/private/jms1/sshca
mkdir 
scp root@vm70:/etc/ssh/ssh_host_\*.pub vm70/
```

Sign the public host keys using the CA.

```
ssh-keygen \
    -s /keybase/private/jms1/sshca/ssh_ca \
    -h \
    -n 'vm70.internal,vm70,172.168.4.70,2001:470:db22::46' \
    -I 'vm70.internal 2019-10-07' \
    -V -5m:+3655d \
    vm70/ssh_host_*.pub
```

* **`-s`** sign using the specified private key

* **`-h`** this is a host key rather than a user key

* **`-n`** is a list of "principals" for which this certificate is valid. Multiple values should be separated using commas.

    For host certificates, each "principal" should be a hostname to which the client would potentially connect. As shown here, this may include the machine's FQDN, the short hostname, and any IP addresses.

    If this option is not specified, the host certificate will be considered valid for all hosts. This *might* be useful in controlled situations (i.e. test labs) but as a general rule you should avoid generating certificates without principals.

* **`-I`** specifies a "key identifier". This is logged by the client when validating the server's certificate. This option is required, and *should be* unique among the certificates issued by the CA. I normally use the machine's FQDN and the date I generated the certificate.

* **`-V`** specifies the time period during which the certificate is valid. The value "`-5m:+3653d`" means "starting five minutes ago, ending in ten years" (at the moment, there are three leap years within the next ten years).

    If this option is not specified, the certificate will never expire.

* **`xxx.pub`** one or more filenames containing the SSH public keys we're signing. If multiple files are specified, multiple certificate files will be created.

The output files will have "`-cert`" added before "`.pub`", so if the public key file is "`xxx.pub`", the certificate file will be "`xxx-cert.pub`".

### Examine the certificate

If you like.

```
ssh-keygen -L -f vm70/ssh_host_rsa_key-cert.pub
```

### Upload the certificates

```
scp ssh_ca.pub vm70/ssh_host_*-cert.pub root@vm70:/etc/ssh/
```

### Trust the CA

Add this line to the `/etc/ssh/sshd_config` file.

```
TrustedUserCAKeys   /etc/ssh/ssh_ca.pub
```

This will make `sshd` trust client certificates signed by the CA.

### Send certificate with server's public key

Add the appropriate `HostCertificate` lines to the `/etc/ssh/sshd_config` file for each certificate.

```
HostCertificate     /etc/ssh/ssh_host_rsa_key-cert.pub
HostCertificate     /etc/ssh/ssh_host_dsa_key-cert.pub
HostCertificate     /etc/ssh/ssh_host_ecdsa_key-cert.pub
HostCertificate     /etc/ssh/ssh_host_ed25519_key-cert.pub
```

I normally put them right below the `Hostkey` lines.

### Allow `.ssh/authorized_principals` files

When using an SSH CA, your users can create an `~/.ssh/authorized_principals` file, which allows them to specify the principals (usernames contained within the certificate, signed by the CA) which are allowed to SSH into that account. It works like an `authorized_keys` file, except that instead of specifying a public key, they can specify a "principal" instead.

Add this line to the `/etc/ssh/sshd_config` file.

```
AuthorizedPrincipalsFile    %h/.ssh/authorized_principals
```

A user's `authorized_principals` file would contain a list of principals, one per line.

```
jms1@jms1.net
command="/bin/false" othername
```

### Fix permissions

```
cd /etc/ssh
chmod 0644 *pub ssh_config
chmod 0600 *key sshd_config
```

If SELinux is active, be sure to set (or reset) the correct contexts on these files.

```
restorecon -rv /etc/ssh/
```

### Restart `sshd`

After changing the `/etc/ssh/sshd_config` file, you need to restart `sshd`.

```
service sshd restart
```

# For each user

### Get the user's SSH public key

The user should generate their own SSH key and send you their `id_rsa.pub` file as usual. Make sure they do NOT send you their private key file.

### Sign the key to produce a certificate

Sign the public key using the CA.

```
ssh-keygen \
    -s /keybase/private/jms1/sshca/ssh_ca \
    -n 'jms1@jms1.net,jms1' \
    -I 'jms1@jms1.net 2019-10-07' \
    -V -5m:+7d \
    id_rsa_jms1@jms1.net.pub
```

* `-s` sign using the specified private key

* `-n` is a list of "principals" for which this certificate is valid. Multiple values should be separated using commas.

    For user certificates, a principal is an arbitrary value which identifies the key or its owner.

    If this option is not specified, (**TODO** not sure?)

* `-I` specifies a "key identifier". This is logged by the server when validating the client's certificate. This option is required.

* `-V` specifies the time period during which the certificate is valid. The value "`-5m:+7d`" means "starting five minutes ago, ending in seven days".

* `xxx.pub` the existing SSH public key we're signing

If the public key file is "`xxx.pub`", the certificate will be written to "`xxx-cert.pub`".

Note that other options can be added to the certificate using the "`-O`" option. Available options include `force-command=xxx`, `no-agent-forwarding`, `no-port-forwarding`, `no-pty`, `no-user-rc`, `no-x11-forwarding`, and `source-address=xxx`. See `ssh-keygen(1)` for details.


## For each user's workstation

### Install the certificate file

Send the `id_rsa-cert.pub` file to the user. They should install it in their `$HOME/.ssh/` directory, using the same base filename as the secret and public key files.

| Filename          | Permissions           | Description
|:------------------|:----------------------|:-------------
| `id_rsa`          | `0600  -rw-------`    | Secret key
| `id_rsa.pub`      | `0644  -rw-r--r--`    | Public key
| `id_rsa-cert.pub` | `0644  -rw-r--r--`    | Certificate

### Configure SSH clients to trust the CA

This makes `ssh`, `scp`, and `sftp` trust any host keys with valid signatures from the CA certificate.

Add the following to `$HOME/.ssh/known_hosts` for an individual user, or `/etc/ssh/ssh_known_hosts` for all users on the system.

```
@cert-authority *.jms1.net ssh-rsa AAAAB3NzaC1y...2Y7NVfsuzQ== sshca@jms1.net
```

The order within the file doesn't seem to matter, but I generally put "`@cert-authority`" lines at the top of the file so they're easy to find.

Notes:

* The "`*.internal`" shown here is a pattern match against the hostname to which the client is connecting. The hostname to which the client is connecting must match this pattern in order for the CA certificate to be trusted.

    If you use "`*`" as the pattern, that CA will be trusted for *all* hostnames. This can be handy if you're used to entering short hostnames and relying on your DNS search domain to "finish" the FQDN. However, doing this is the same thing as adding a CA to your browser's list of trusted roots - you should only do this with CA certificates you fully trust (or in my case, that I control).

* Adding the same line to `/etc/ssh/ssh_config` will make the same change system-wide, i.e. *all* SSH clients on the machine will trust host certificates signed by that CA.

# References

* [`https://jameshfisher.com/2018/03/16/how-to-create-an-ssh-certificate-authority/`](https://jameshfisher.com/2018/03/16/how-to-create-an-ssh-certificate-authority/)

* [`https://smallstep.com/blog/use-ssh-certificates/`](https://smallstep.com/blog/use-ssh-certificates/)

* [`https://www.lorier.net/docs/ssh-ca.html`](https://www.lorier.net/docs/ssh-ca.html)
