diff --git a/.qubesbuilder b/.qubesbuilder index dfc4b06..ad7a7a1 100644 --- a/.qubesbuilder +++ b/.qubesbuilder @@ -43,6 +43,7 @@ host: - rpm_spec/qusal-sys-gui-vnc.spec - rpm_spec/qusal-sys-mirage-firewall.spec - rpm_spec/qusal-sys-net.spec + - rpm_spec/qusal-sys-pgp.spec - rpm_spec/qusal-sys-pihole.spec - rpm_spec/qusal-sys-print.spec - rpm_spec/qusal-sys-rsync.spec diff --git a/salt/sys-pgp/README.md b/salt/sys-pgp/README.md index 66b8422..c999924 100644 --- a/salt/sys-pgp/README.md +++ b/salt/sys-pgp/README.md @@ -6,8 +6,16 @@ PGP operations through Qrexec in Qubes OS. * [Description](#description) * [Installation](#installation) + * [Passphrase](#passphrase) + * [Client API libraries](#client-api-libraries) * [Access Control](#access-control) * [Usage](#usage) + * [Service activation](#service-activation) + * [Key management on the server](#key-management-on-the-server) + * [Passphrase protection](#passphrase-protection) + * [Generate new keys](#generate-new-keys) + * [Import existing keys](#import-existing-keys) + * [Split key usage on the client](#split-key-usage-on-the-client) ## Description @@ -51,6 +59,45 @@ The client qube requires the split GPG client service to be enabled: qvm-features QUBE service.split-gpg2-client 1 ``` +### Passphrase + +In case you plan to use passphrase, install a GUI pinentry: + +```sh +sudo qubesctl --skip-dom0 --targets=tpl-sys-pgp state.apply sys-pgp.install-pinentry +``` + +### Client API libraries + +If you are using an application that interacts using the GnuPG API instead of +the command-line such as Thunderbird, you will need to install on the client +a GPGME package specific to your client application. This is not covered by +default. + +Install GPGME C API library on the client template: + +```sh +sudo qubesctl --skip-dom0 --targets=tpl-qubes-builder,tpl-dev state.apply sys-pgp.install-client-gpgme-c +``` + +Install GPGME C++ API library on the client template: + +```sh +sudo qubesctl --skip-dom0 --targets=tpl-qubes-builder,tpl-dev state.apply sys-pgp.install-client-gpgme-c++ +``` + +Install GPGME Qt API library on the client template: + +```sh +sudo qubesctl --skip-dom0 --targets=tpl-qubes-builder,tpl-dev state.apply sys-pgp.install-client-gpgme-qt +``` + +Install GPGME Python API library on the client template: + +```sh +sudo qubesctl --skip-dom0 --targets=tpl-qubes-builder,tpl-dev state.apply sys-pgp.install-client-gpgme-python +``` + ## Access Control _Default policy_: `any qube` can `ask` via the `@default` target if you allow @@ -69,8 +116,10 @@ qubes.Gpg2 * @anyvm @anyvm deny Consult [upstream documentation](https://github.com/QubesOS/qubes-app-linux-split-gpg2) on how to use split-gpg2. -Save your PGP keys to `sys-pgp`, using isolated GnuPG home directory per qube -at `~/.gnupg/split-gpg/`. +On the following examples, we will consider `dev` as the client qube and +`ben` as the key user ID. + +### Service activation On `dom0`, enabled the service `split-gpg2-client` for the client qube `dev`: @@ -78,7 +127,49 @@ On `dom0`, enabled the service `split-gpg2-client` for the client qube `dev`: qvm-features dev service.split-gpg2-client 1 ``` -On the qube `sys-pgp`, generate or import keys for the client qube `dev`: +### Key management on the server + +#### Passphrase protection + +Save your PGP keys to `sys-pgp`, using isolated GnuPG home directory per qube +at `~/.gnupg/split-gpg/`. + +Please note that adding a passphrase brings +[no additional value](https://www.qubes-os.org/doc/split-gpg): + +> Having a passphrase on the key is of little value. An adversary who is +> capable of stealing the key from your vault would almost certainly also be +> capable of stealing the passphrase as you enter it. An adversary who +> obtains the passphrase can then use it in order to change or remove the +> passphrase from the key. + +Generate a private keys without a passphrase, use the following when +generating a key pair: `--pinentry-mode loopback --passphrase ""` + +If you have already set a passphrase for your private key, you can delete it +by providing the current passphrase to unlock the key, confirming and then +clicking `OK` with an empty passphrase (the dialog might appear twice): + +```sh +gpg --homedir ~/.gnupg/split-gpg/dev --edit-key ben passwd +``` + +#### Generate new keys + +You should use subkeys, but configuring this key type is for advanced users +and out of scope for this document. Please refer to an external source. + +On the qube `sys-pgp`, generate keys for the client qube `dev`: + +```sh +mkdir -p -- ~/.gnupg/split-gpg/dev +gpg --homedir ~/.gnupg/split-gpg/dev --pinentry-mode loopback --passphrase "" --gen-key +gpg --homedir ~/.gnupg/split-gpg/dev --list-secret-keys +``` + +#### Import existing keys + +On the qube `sys-pgp`, import keys for the client qube `dev`: ```sh mkdir -p -- ~/.gnupg/split-gpg/dev @@ -86,8 +177,16 @@ gpg --homedir ~/.gnupg/split-gpg/dev --import /path/to/secret.key gpg --homedir ~/.gnupg/split-gpg/dev --list-secret-keys ``` -On the qube `dev`, import the public part of your key: +### Split key usage on the client + +On the client qube `dev`, import the public part of your key: ```sh gpg --import /path/to/public.key ``` + +You should now have access to see the secret keys fingerprints: + +```sh +gpg --list-secret-keys +``` diff --git a/salt/sys-pgp/install-client-gpgme-c++.sls b/salt/sys-pgp/install-client-gpgme-c++.sls new file mode 100644 index 0000000..ae0501d --- /dev/null +++ b/salt/sys-pgp/install-client-gpgme-c++.sls @@ -0,0 +1,30 @@ +{# +SPDX-FileCopyrightText: 2025 Benjamin Grande M. S. + +SPDX-License-Identifier: AGPL-3.0-or-later +#} + +{% if grains['nodename'] != 'dom0' -%} + +include: + - utils.tools.common.update + +{% set pkg = { + 'Debian': { + 'pkg': ['libgpgmepp6'], + }, + 'RedHat': { + 'pkg': ['gpgmepp'], + }, +}.get(grains.os_family) -%} + +"{{ slsdotpath }}-client-installed-os-specific-gpgme-c++": + pkg.installed: + - require: + - sls: utils.tools.common.update + - install_recommends: False + - skip_suggestions: True + - setopt: "install_weak_deps=False" + - pkgs: {{ pkg.pkg|sequence|yaml }} + +{% endif -%} diff --git a/salt/sys-pgp/install-client-gpgme-c++.top b/salt/sys-pgp/install-client-gpgme-c++.top new file mode 100644 index 0000000..38a5c89 --- /dev/null +++ b/salt/sys-pgp/install-client-gpgme-c++.top @@ -0,0 +1,10 @@ +{# +SPDX-FileCopyrightText: 2025 Benjamin Grande M. S. + +SPDX-License-Identifier: AGPL-3.0-or-later +#} + +base: + '*': + - match: list + - sys-pgp.install-client-gpgme-c++ diff --git a/salt/sys-pgp/install-client-gpgme-c.sls b/salt/sys-pgp/install-client-gpgme-c.sls new file mode 100644 index 0000000..43330b1 --- /dev/null +++ b/salt/sys-pgp/install-client-gpgme-c.sls @@ -0,0 +1,30 @@ +{# +SPDX-FileCopyrightText: 2025 Benjamin Grande M. S. + +SPDX-License-Identifier: AGPL-3.0-or-later +#} + +{% if grains['nodename'] != 'dom0' -%} + +include: + - utils.tools.common.update + +{% set pkg = { + 'Debian': { + 'pkg': ['libgpgme11'], + }, + 'RedHat': { + 'pkg': ['gpgme'], + }, +}.get(grains.os_family) -%} + +"{{ slsdotpath }}-client-installed-os-specific-gpgme-c": + pkg.installed: + - require: + - sls: utils.tools.common.update + - install_recommends: False + - skip_suggestions: True + - setopt: "install_weak_deps=False" + - pkgs: {{ pkg.pkg|sequence|yaml }} + +{% endif -%} diff --git a/salt/sys-pgp/install-client-gpgme-c.top b/salt/sys-pgp/install-client-gpgme-c.top new file mode 100644 index 0000000..9dfe71b --- /dev/null +++ b/salt/sys-pgp/install-client-gpgme-c.top @@ -0,0 +1,10 @@ +{# +SPDX-FileCopyrightText: 2025 Benjamin Grande M. S. + +SPDX-License-Identifier: AGPL-3.0-or-later +#} + +base: + '*': + - match: list + - sys-pgp.install-client-gpgme-c diff --git a/salt/sys-pgp/install-client-gpgme-python.sls b/salt/sys-pgp/install-client-gpgme-python.sls new file mode 100644 index 0000000..61f4694 --- /dev/null +++ b/salt/sys-pgp/install-client-gpgme-python.sls @@ -0,0 +1,30 @@ +{# +SPDX-FileCopyrightText: 2025 Benjamin Grande M. S. + +SPDX-License-Identifier: AGPL-3.0-or-later +#} + +{% if grains['nodename'] != 'dom0' -%} + +include: + - utils.tools.common.update + +{% set pkg = { + 'Debian': { + 'pkg': ['python3-gpg'], + }, + 'RedHat': { + 'pkg': ['python3-gpg'], + }, +}.get(grains.os_family) -%} + +"{{ slsdotpath }}-client-installed-os-specific-gpgme-python": + pkg.installed: + - require: + - sls: utils.tools.common.update + - install_recommends: False + - skip_suggestions: True + - setopt: "install_weak_deps=False" + - pkgs: {{ pkg.pkg|sequence|yaml }} + +{% endif -%} diff --git a/salt/sys-pgp/install-client-gpgme-python.top b/salt/sys-pgp/install-client-gpgme-python.top new file mode 100644 index 0000000..7f6bf7a --- /dev/null +++ b/salt/sys-pgp/install-client-gpgme-python.top @@ -0,0 +1,10 @@ +{# +SPDX-FileCopyrightText: 2025 Benjamin Grande M. S. + +SPDX-License-Identifier: AGPL-3.0-or-later +#} + +base: + '*': + - match: list + - sys-pgp.install-client-python diff --git a/salt/sys-pgp/install-client-gpgme-qt.sls b/salt/sys-pgp/install-client-gpgme-qt.sls new file mode 100644 index 0000000..eb7aff9 --- /dev/null +++ b/salt/sys-pgp/install-client-gpgme-qt.sls @@ -0,0 +1,35 @@ +{# +SPDX-FileCopyrightText: 2025 Benjamin Grande M. S. + +SPDX-License-Identifier: AGPL-3.0-or-later +#} + +{% if grains['nodename'] != 'dom0' -%} + +include: + - utils.tools.common.update + +{% set pkg = { + 'Debian': { + 'pkg': ['libqgpgme15'], + }, + 'RedHat': { + 'pkg': + {% if grains.os == 'Fedora' and grains.osmajorrelease | int >= 40 %} + ['qgpgme-qt5', 'qgpgme-qt6'], + {% else %} + ['qgpgme'], + {% endif %} + }, +}.get(grains.os_family) -%} + +"{{ slsdotpath }}-client-installed-os-specific-gpgme-qt": + pkg.installed: + - require: + - sls: utils.tools.common.update + - install_recommends: False + - skip_suggestions: True + - setopt: "install_weak_deps=False" + - pkgs: {{ pkg.pkg|sequence|yaml }} + +{% endif -%} diff --git a/salt/sys-pgp/install-client-gpgme-qt.top b/salt/sys-pgp/install-client-gpgme-qt.top new file mode 100644 index 0000000..13265dc --- /dev/null +++ b/salt/sys-pgp/install-client-gpgme-qt.top @@ -0,0 +1,10 @@ +{# +SPDX-FileCopyrightText: 2025 Benjamin Grande M. S. + +SPDX-License-Identifier: AGPL-3.0-or-later +#} + +base: + '*': + - match: list + - sys-pgp.install-client-qt diff --git a/salt/sys-pgp/install-client.sls b/salt/sys-pgp/install-client.sls index 88f42c6..323a08f 100644 --- a/salt/sys-pgp/install-client.sls +++ b/salt/sys-pgp/install-client.sls @@ -1,5 +1,5 @@ {# -SPDX-FileCopyrightText: 2023 Benjamin Grande M. S. +SPDX-FileCopyrightText: 2025 Benjamin Grande M. S. SPDX-License-Identifier: AGPL-3.0-or-later #} diff --git a/salt/sys-pgp/install-client.top b/salt/sys-pgp/install-client.top index 78af79f..11e9781 100644 --- a/salt/sys-pgp/install-client.top +++ b/salt/sys-pgp/install-client.top @@ -1,5 +1,5 @@ {# -SPDX-FileCopyrightText: 2023 Benjamin Grande M. S. +SPDX-FileCopyrightText: 2025 Benjamin Grande M. S. SPDX-License-Identifier: AGPL-3.0-or-later #} diff --git a/salt/sys-pgp/install-pinentry.sls b/salt/sys-pgp/install-pinentry.sls new file mode 100644 index 0000000..29a799c --- /dev/null +++ b/salt/sys-pgp/install-pinentry.sls @@ -0,0 +1,30 @@ +{# +SPDX-FileCopyrightText: 2025 Benjamin Grande M. S. + +SPDX-License-Identifier: AGPL-3.0-or-later +#} + +{% if grains['nodename'] != 'dom0' -%} + +include: + - utils.tools.common.update + +{% set pkg = { + 'Debian': { + 'pkg': ['pinentry-curses', 'pinentry-tty', 'pinentry-gnome3'], + }, + 'RedHat': { + 'pkg': ['pinentry', 'pinentry-tty', 'pinentry-gnome3'], + }, +}.get(grains.os_family) -%} + +"{{ slsdotpath }}-installed-os-specific-pinentry": + pkg.installed: + - require: + - sls: utils.tools.common.update + - install_recommends: False + - skip_suggestions: True + - setopt: "install_weak_deps=False" + - pkgs: {{ pkg.pkg|sequence|yaml }} + +{% endif -%} diff --git a/salt/sys-pgp/install-pinentry.top b/salt/sys-pgp/install-pinentry.top new file mode 100644 index 0000000..a75bfc0 --- /dev/null +++ b/salt/sys-pgp/install-pinentry.top @@ -0,0 +1,9 @@ +{# +SPDX-FileCopyrightText: 2025 Benjamin Grande M. S. + +SPDX-License-Identifier: AGPL-3.0-or-later +#} + +base: + 'tpl-sys-pgp': + - sys-pgp.install-pinentry