<!--
SPDX-FileCopyrightText: 2024 David Runge <dave@sleepmap.de>
SPDX-FileCopyrightText: 2024 Wiktor Kwapisiewicz <wiktor@metacode.biz>
SPDX-License-Identifier: MIT OR Apache-2.0
-->

# `sshd-openpgp-auth`

This tool provides server-side functionality to create and manage OpenPGP certificates that serve as trust anchors for SSH host keys.

More specifically, `sshd-openpgp-auth` transparently manages the lifecycle of an OpenPGP certificate and the validity of SSH host keys as its authentication subkeys.
Its featureful commandline interface allows for import of host keys from SSH public key files as well as [known_hosts file format] formatted strings, extension of expiry period of the primary key, revocation of any of the subkeys and deployment to a [Web Key Directory (WKD)].

As complementary client-side tool `ssh-openpgp-auth` is used to validate the trust anchors and OpenPGP subkeys created with `sshd-openpgp-auth`.

## Installation

To install this tool use cargo:

```shell
cargo install sshd-openpgp-auth
```

## Dependencies

This tool is able to add subkeys from existing SSH host keys on a system.
For this only access to the SSH public keys is required.

When importing the public SSH host keys of remote machines (in [known_hosts file format]), the output of [`ssh-keyscan`] (usually part of an [OpenSSH] installation) can be used directly.

## Using `sshd-openpgp-auth`

This tool does not require a configuration file and relies on defaults which can be overridden on the command-line and using dedicated environment variables.

By default SSH public keys are searched for in `/etc/ssh/` and OpenPGP certificates are stored in `/var/lib/sshd-openpgp-auth/`.
As such, the tool should be run as a separate system user when using the default OpenPGP certificate location!
This repository offers a [sysusers.d] and [tmpfiles.d] integration in the [contrib](/contrib/) dir for this scenario.

However, it is entirely possible to manage the trust anchors of *other hosts* and rely on one's own user for this with the help of the `--openpgp-dir` and `--fingerprint` options to define the directory for storing OpenPGP certificates and identifying specific OpenPGP certificates with the help of an OpenPGP fingerprint.

The following subsections provide a broad overview of the various commands `sshd-openpgp-auth` offers.
For a more detailed overview refer to the command's `--help` output.

### Initializing a trust anchor

To start offering OpenPGP based authentication for the SSH setup of a host, one needs to create an OpenPGP certificate, which serves as trust anchor.

For demonstration purposes let us assume, that we are working with a custom temporary storage location for our OpenPGP certificates, are not using the system's SSH host keys (generating the host keys requires [`ssh-keygen`], usually part of [OpenSSH]) and want to work on a host named `example.com`.

```sh
export SSH_DIR="$(mktemp -d)"
export SOA_SSH_DIR="$SSH_DIR/etc/ssh/"
export LOCAL_SSH_DIR="$SOA_SSH_DIR"
export SOA_OPENPGP_DIR="$(mktemp -d)"
export LOCAL_OPENPGP_DIR="$SOA_OPENPGP_DIR"

mkdir -vp "$SOA_SSH_DIR"
ssh-keygen -A -f "$SSH_DIR"
sleep 2
sshd-openpgp-auth init "example.com"
sshd-openpgp-auth list

sq inspect --certifications "$SOA_OPENPGP_DIR/"*.tsk
```

### Adding SSH host keys

The SSH host keys of a system can be added to an OpenPGP certificate in two ways: Locally and remotely.

#### Local host keys

To add the local SSH host keys the `add` subcommand is used.

```sh
sshd-openpgp-auth add
sshd-openpgp-auth list

sq inspect --certifications "$SOA_OPENPGP_DIR/"*.tsk

unset SOA_SSH_DIR
```

#### Remote host keys

Let us assume we are [codeberg.org] and want to use `sshd-openpgp-auth` to add the service's SSH public keys to a trust anchor on a different host.
This example requires [`ssh-keyscan`].

```sh
export PREV_SOA_OPENPGP_DIR="$SOA_OPENPGP_DIR"
export SOA_OPENPGP_DIR="$(mktemp -d)"

sshd-openpgp-auth init "codeberg.org"
sshd-openpgp-auth list
set +o pipefail
ssh-keyscan -t ecdsa,ed25519,rsa codeberg.org | sshd-openpgp-auth add --known-hosts
set -o pipefail
sshd-openpgp-auth list

sq inspect --certifications "$SOA_OPENPGP_DIR/"*.tsk

export REMOTE_OPENPGP_DIR="$SOA_OPENPGP_DIR"
export SOA_OPENPGP_DIR="$PREV_SOA_OPENPGP_DIR"
```

### Adding thirdparty certifications

The client-side tool `ssh-openpgp-auth` can validate the authenticity of a host's OpenPGP certificate based on PGPKI (aka the [Web of Trust]).
In this scenario the user of the client tool usually trusts a central [certificate authority (CA)]: Another OpenPGP certificate holder that certifies the host's OpenPGP certificate.

Let us assume that the host `example.com` will be certified by an OpenPGP certificate with the User ID `admin@central-ca.com`.

```sh
export CA_TMPDIR="$(mktemp -d)"

# create a temporary CA key
sq key generate --userid 'SSH CA <admin@central-ca.com>' --output "$CA_TMPDIR/ca_key.tsk"
sq inspect --certifications "$CA_TMPDIR/ca_key.tsk"

# certify the host's key using the CA key
sq pki certify --output "$CA_TMPDIR/example_certification.pgp" "$CA_TMPDIR/ca_key.tsk" "$SOA_OPENPGP_DIR/"*.tsk "<ssh-openpgp-auth@example.com>"
sq inspect --certifications "$CA_TMPDIR/example_certification.pgp"
```

It is now possible to directly import the created certification by merging it with the host's [Transferable Secret Key] ([TSK]).

```sh
sshd-openpgp-auth merge "$CA_TMPDIR/example_certification.pgp"
sq inspect --certifications "$SOA_OPENPGP_DIR/"*.tsk
```

Managing thirdparty certifications such as the one added above in the host's [TSK] has the upside, that certifications are directly available to clients upon exporting the host key to [WKD].

### Exporting to Web Key Directory

One of the upsides of `sshd-openpgp-auth` is, that it can be used in the context of the already existing OpenPGP based ecosystem for certificate retrieval: [WKD].

Using the `export` subcommand, certificates can be exported to a local directory, which can directly be served by a web server.

```sh
export SOA_WKD_OUTPUT_DIR="$(mktemp -d)"
sshd-openpgp-auth export "example.com"

export SOA_WKD_OUTPUT_DIR="$(mktemp -d)"
sshd-openpgp-auth export "example.com" --wkd-type "direct"
```

### Extend the expiry period

When using this tool all added subkeys depend on the expiration time of the OpenPGP primary key.

Using the `extend` subcommand the primary key can be extended.

Let us assume, the administrator wants to extend the OpenPGP certificate's expiry period to a duration longer than the default.

```sh
sshd-openpgp-auth extend --threshold 365 --expiry 730
sshd-openpgp-auth list

sq inspect --certifications "$SOA_OPENPGP_DIR/"*.tsk
```

### Revoking authentication subkeys

Sometimes it is necessary to revoke one or all of the authentication subkeys.
Reasons for this may be, that the administrator wants to create new SSH host keys with stronger cryptographic algorithms, deprecate old ones or (worst case) has to replace compromised ones.

Let us have a look at the different scenarios in the following subsections.

#### The key has been superseded

This is the default revocation action and used in scenarios where new OpenPGP authentication subkeys replace old ones.

Let us again imagine we are [codeberg.org] and want to replace our current SSH host keys with new ones.
We will add newly created SSH host keys and then revoke the old ones while appending a message that points at the new subkeys.

```sh
export SOA_OPENPGP_DIR="$REMOTE_OPENPGP_DIR"
export SOA_SSH_DIR="$LOCAL_SSH_DIR"

sshd-openpgp-auth list
sshd-openpgp-auth add
sshd-openpgp-auth list

export REMOTE_SUBKEYS=( $(sshd-openpgp-auth list | grep '✅' | cut -f 2 -d ' ') )

sshd-openpgp-auth revoke --subkey-fingerprint "${REMOTE_SUBKEYS[0]}" --message "Superseded by ${REMOTE_SUBKEYS[1]}"
sshd-openpgp-auth revoke --subkey-fingerprint "${REMOTE_SUBKEYS[2]}" --message "Superseded by ${REMOTE_SUBKEYS[3]}"
sshd-openpgp-auth revoke --subkey-fingerprint "${REMOTE_SUBKEYS[4]}" --message "Superseded by ${REMOTE_SUBKEYS[5]}"
sshd-openpgp-auth list

sq inspect --certifications "$SOA_OPENPGP_DIR/"*.tsk
```

#### The key is retired

For this example let us assume that we want to retire only a single SSH host key (e.g. because we do not support the algorithm in question anymore).

```sh
export SOA_OPENPGP_DIR="$LOCAL_OPENPGP_DIR"

sshd-openpgp-auth list

export REMOTE_SUBKEYS=( $(sshd-openpgp-auth list | grep '✅' | cut -f 2 -d ' ') )

sshd-openpgp-auth revoke --subkey-fingerprint "${REMOTE_SUBKEYS[0]}" --reason retired --message "We stopped supporting RSA"
sshd-openpgp-auth list

sq inspect --certifications "$SOA_OPENPGP_DIR/"*.tsk
```

#### The key is compromised

Let us also have a look at our worst-case scenario: Our host has been compromised and an attacker stole our SSH host keys to be able to impersonate our host.

```sh
sshd-openpgp-auth revoke --all --reason compromised --message "This host has been compromised today!"
sshd-openpgp-auth list

sq inspect --certifications "$SOA_OPENPGP_DIR/"*.tsk
```

### Adding Keyoxide DNS proof

To increase trust in the host certificate it's also possible to insert [Keyoxide](https://keyoxide.org/) proof of authenticity:

```sh
sshd-openpgp-auth proof dns add
```

The above will add a notation to the certificate, which represents a claim about the ownership of the domain the certificate is used for. Clients can retrieve the certificate via WKD or OpenPGP keyservers and attempt to validate this claim.

To allow clients to validate the identity claim added with the notation, specific proof must be added to the DNS zone file of the host as a `TXT` record in the following format:

```
openpgp4fpr:FINGERPRINT
```

Where `FINGERPRINT` is a lower-cased hexadecimal representation of the key's fingerprint.

An example of several authorized keys:

```bash
$ dig +short TXT metacode.biz
"openpgp4fpr:0e3bb828432962f4e33c9a74d1f809bb3f02ede9"
"openpgp4fpr:198c722a4bac336e9daaae44579d01b3abe1540e"
"openpgp4fpr:653909a2f0e37c106f5faf546c8857e0d8e8f074"
```

For [OpenPGP CA](https://openpgp-ca.org/) installations the fingerprint of the OpenPGP CA certificate should also be present in the `TXT` records of the DNS zone.

For more technical details see [Keyoxide DNS proof](https://docs.keyoxide.org/service-providers/dns/) page.

## Funding

This project is funded through [NGI Assure](https://nlnet.nl/assure), a fund established by [NLnet](https://nlnet.nl) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu) program. Learn more at the [NLnet project page](https://nlnet.nl/OpenPGP-OpenSSH).

[<img src="https://nlnet.nl/logo/banner.png" alt="NLnet foundation logo" width="20%" />](https://nlnet.nl)
[<img src="https://nlnet.nl/image/logos/NGIAssure_tag.svg" alt="NGI Assure Logo" width="20%" />](https://nlnet.nl/assure)

## License

This project is licensed under either of:

  - [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0),
  - [MIT license](https://opensource.org/licenses/MIT).

at your option.

### Contribution

Unless you explicitly state otherwise, any contribution intentionally
submitted for inclusion in this crate by you, as defined in the
Apache-2.0 license, shall be dual licensed as above, without any
additional terms or conditions.

[known_hosts file format]: https://man.archlinux.org/man/sshd.8#SSH_KNOWN_HOSTS_FILE_FORMAT
[Web Key Directory (WKD)]: https://datatracker.ietf.org/doc/html/draft-koch-openpgp-webkey-service#name-web-key-directory
[WKD]: https://datatracker.ietf.org/doc/html/draft-koch-openpgp-webkey-service#name-web-key-directory
[`ssh-keyscan`]: https://man.archlinux.org/man/ssh-keyscan.1
[OpenSSH]: https://www.openssh.com/portable.html
[sysusers.d]: https://man.archlinux.org/man/sysusers.d.5
[tmpfiles.d]: https://man.archlinux.org/man/tmpfiles.d.5
[`ssh-keygen`]: https://man.archlinux.org/man/ssh-keygen.1
[codeberg.org]: https://codeberg.org
[Web of Trust]: https://en.wikipedia.org/wiki/Web_of_trust
[certificate authority (CA)]: https://en.wikipedia.org/wiki/Certificate_authority
[Transferable Secret Key]: https://openpgp.dev/book/glossary.html#term-Transferable-Secret-Key
[TSK]: https://openpgp.dev/book/glossary.html#term-Transferable-Secret-Key
