diff --git a/modules/programs/gpg.nix b/modules/programs/gpg.nix
index d32a81924..31ddf2e8c 100644
--- a/modules/programs/gpg.nix
+++ b/modules/programs/gpg.nix
@@ -21,6 +21,110 @@ let
} cfg.scdaemonSettings;
primitiveType = types.oneOf [ types.str types.bool ];
+
+ publicKeyOpts = { config, ...}: {
+ options = {
+ text = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Text of an OpenPGP public key.
+ '';
+ };
+
+ source = mkOption {
+ type = types.path;
+ description = ''
+ Path of an OpenPGP public key file.
+ '';
+ };
+
+ trust = mkOption {
+ type = types.nullOr (types.enum [ 1 2 3 4 5 ]);
+ default = null;
+ description = ''
+ The amount of trust you have in the key ownership and the care the
+ owner puts into signing other keys. The available levels are
+
+
+ 1
+ I don't know or won't say.
+
+
+ 2
+ I do NOT trust.
+
+
+ 3
+ I trust marginally.
+
+
+ 4
+ I trust fully.
+
+
+ 5
+ I trust ultimately.
+
+
+
+ See
+ for more.
+ '';
+ };
+ };
+
+ config = {
+ source = mkIf (config.text != null)
+ (pkgs.writeText "gpg-pubkey" config.text);
+ };
+ };
+
+ importTrustBashFunctions =
+ let gpg = "${cfg.package}/bin/gpg";
+ in ''
+ function gpgKeyId() {
+ ${gpg} --show-key --with-colons "$1" \
+ | grep ^pub: \
+ | cut -d: -f5
+ }
+
+ function importTrust() {
+ local keyId trust
+ keyId="$(gpgKeyId "$1")"
+ trust="$2"
+ if [[ -n $keyId ]] ; then
+ echo -e "trust\n$trust\ny\nquit" \
+ | ${gpg} --no-tty --command-fd 0 --edit-key "$keyId"
+ fi
+ }
+ '';
+
+ keyringFiles =
+ let
+ gpg = "${cfg.package}/bin/gpg";
+
+ importKey = { source, trust, ... }: ''
+ ${gpg} --import ${source}
+ ${optionalString (trust != null) ''
+ importTrust "${source}" ${toString trust}''}
+ '';
+
+ importKeys = concatMapStringsSep "\n" importKey cfg.publicKeys;
+ in pkgs.runCommand "gpg-pubring" { buildInputs = [ cfg.package ]; } ''
+ export GNUPGHOME
+ GNUPGHOME=$(mktemp -d)
+
+ ${importTrustBashFunctions}
+ ${importKeys}
+
+ mkdir $out
+ cp $GNUPGHOME/pubring.kbx $out/pubring.kbx
+ if [[ -e $GNUPGHOME/trustdb.gpg ]] ; then
+ cp $GNUPGHOME/trustdb.gpg $out/trustdb.gpg
+ fi
+ '';
+
in
{
options.programs.gpg = {
@@ -73,6 +177,48 @@ in
defaultText = literalExpression "\"\${config.home.homeDirectory}/.gnupg\"";
description = "Directory to store keychains and configuration.";
};
+
+ mutableKeys = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ If set to true, you may manage your keyring as a user
+ using the gpg command. Upon activation, the keyring
+ will have managed keys added without overwriting unmanaged keys.
+
+ If set to false, the path
+ $GNUPGHOME/pubring.kbx will become an immutable
+ link to the Nix store, denying modifications.
+ '';
+ };
+
+ mutableTrust = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ If set to true, you may manage trust as a user using
+ the gpg command. Upon activation, trusted keys have
+ their trust set without overwriting unmanaged keys.
+
+ If set to false, the path
+ $GNUPGHOME/trustdb.gpg will be
+ overwritten on each activation, removing trust for
+ any unmanaged keys. Be careful to make a backup of your old
+ trustdb.gpg before switching to immutable trust!
+ '';
+ };
+
+ publicKeys = mkOption {
+ type = types.listOf (types.submodule publicKeyOpts);
+ example = literalExpression ''
+ [ { source = ./pubkeys.txt; } ]
+ '';
+ default = [ ];
+ description = ''
+ A list of public keys to be imported into GnuPG. Note, these key files
+ will be copied into the world-readable Nix store.
+ '';
+ };
};
config = mkIf cfg.enable {
@@ -109,5 +255,48 @@ in
home.file."${cfg.homedir}/gpg.conf".text = cfgText;
home.file."${cfg.homedir}/scdaemon.conf".text = scdaemonCfgText;
+
+ # Link keyring if keys are not mutable
+ home.file."${cfg.homedir}/pubring.kbx" =
+ mkIf (!cfg.mutableKeys && cfg.publicKeys != []) {
+ source = "${keyringFiles}/pubring.kbx";
+ };
+
+ home.activation = mkIf (cfg.publicKeys != []) {
+ importGpgKeys =
+ let
+ gpg = "${cfg.package}/bin/gpg";
+
+ importKey = { source, trust, ... }:
+ # Import mutable keys
+ optional cfg.mutableKeys ''
+ $DRY_RUN_CMD ${gpg} $QUIET_ARG --import ${source}''
+
+ # Import mutable trust
+ ++ optional (trust != null && cfg.mutableTrust) ''
+ $DRY_RUN_CMD importTrust "${source}" ${toString trust}'';
+
+ anyTrust = any (k: k.trust != null) cfg.publicKeys;
+
+ importKeys = concatStringsSep "\n" (concatMap importKey cfg.publicKeys);
+
+ # If any key/trust should be imported then create the block. Otherwise
+ # leave it empty.
+ block = concatStringsSep "\n" (
+ optional (importKeys != "") ''
+ export GNUPGHOME=${escapeShellArg cfg.homedir}
+ if [[ ! -v VERBOSE ]]; then
+ QUIET_ARG="--quiet"
+ else
+ QUIET_ARG=""
+ fi
+ ${importTrustBashFunctions}
+ ${importKeys}
+ unset GNUPGHOME QUIET_ARG keyId importTrust
+ '' ++ optional (!cfg.mutableTrust && anyTrust) ''
+ install -m 0700 ${keyringFiles}/trustdb.gpg "${cfg.homedir}/trustdb.gpg"''
+ );
+ in lib.hm.dag.entryAfter ["linkGeneration"] block;
+ };
};
}
diff --git a/tests/modules/programs/gpg/default.nix b/tests/modules/programs/gpg/default.nix
index 7fed2cdcc..a3949b186 100644
--- a/tests/modules/programs/gpg/default.nix
+++ b/tests/modules/programs/gpg/default.nix
@@ -1 +1,5 @@
-{ gpg-override-defaults = ./override-defaults.nix; }
+{
+ gpg-immutable-keyfiles = ./immutable-keyfiles.nix;
+ gpg-mutable-keyfiles = ./mutable-keyfiles.nix;
+ gpg-override-defaults = ./override-defaults.nix;
+}
diff --git a/tests/modules/programs/gpg/immutable-keyfiles.nix b/tests/modules/programs/gpg/immutable-keyfiles.nix
new file mode 100644
index 000000000..d75ff5204
--- /dev/null
+++ b/tests/modules/programs/gpg/immutable-keyfiles.nix
@@ -0,0 +1,52 @@
+{ config, lib, pkgs, ... }:
+
+{
+ programs.gpg = {
+ enable = true;
+
+ mutableKeys = false;
+ mutableTrust = false;
+
+ publicKeys = [
+ {
+ source = pkgs.fetchurl {
+ url =
+ "https://keybase.io/rycee/pgp_keys.asc?fingerprint=36cacf52d098cc0e78fb0cb13573356c25c424d4";
+ sha256 = "082mjy6llvrdry6i9r5gx97nw9d89blnam7bghza4ynsjk1mmx6c";
+ };
+ trust = 1;
+ }
+ {
+ source = pkgs.fetchurl {
+ url = "https://www.rsync.net/resources/pubkey.txt";
+ sha256 = "16nzqfb1kvsxjkq919hxsawx6ydvip3md3qyhdmw54qx6drnxckl";
+ };
+ trust = 2;
+ }
+ ];
+ };
+
+ nmt.script = ''
+ assertFileNotRegex activate "^export GNUPGHOME='/home/hm-user/.gnupg'$"
+
+ assertFileRegex activate \
+ '^install -m 0700 /nix/store/[0-9a-z]*-gpg-pubring/trustdb.gpg "/home/hm-user/.gnupg/trustdb.gpg"$'
+
+ # Setup GPGHOME
+ export GNUPGHOME=$(mktemp -d)
+ cp -r $TESTED/home-files/.gnupg/* $GNUPGHOME
+ TRUSTDB=$(grep -o '/nix/store/[0-9a-z]*-gpg-pubring/trustdb.gpg' $TESTED/activate)
+ install -m 0700 $TRUSTDB $GNUPGHOME/trustdb.gpg
+
+ # Export Trust
+ export WORKDIR=$(mktemp -d)
+ ${pkgs.gnupg}/bin/gpg -q --export-ownertrust > $WORKDIR/gpgtrust.txt
+
+ # Check Trust
+ assertFileRegex $WORKDIR/gpgtrust.txt \
+ '^36CACF52D098CC0E78FB0CB13573356C25C424D4:2:$'
+
+ assertFileRegex $WORKDIR/gpgtrust.txt \
+ '^BB847B5A69EF343CEF511B29073C282D7D6F806C:3:$'
+ '';
+}
diff --git a/tests/modules/programs/gpg/mutable-keyfiles.nix b/tests/modules/programs/gpg/mutable-keyfiles.nix
new file mode 100644
index 000000000..588c90704
--- /dev/null
+++ b/tests/modules/programs/gpg/mutable-keyfiles.nix
@@ -0,0 +1,30 @@
+{ config, lib, pkgs, ... }:
+
+{
+ programs.gpg = {
+ enable = true;
+
+ publicKeys = [
+ {
+ source = builtins.toFile "key1" "key1";
+ trust = 1;
+ }
+ { source = builtins.toFile "key2" "key2"; }
+ ];
+ };
+
+ test.stubs.gnupg = { };
+
+ nmt.script = ''
+ assertFileContains activate "export GNUPGHOME='/home/hm-user/.gnupg'"
+
+ assertFileContains activate "unset GNUPGHOME QUIET_ARG keyId importTrust"
+
+ assertFileRegex activate \
+ '^\$DRY_RUN_CMD @gnupg@/bin/gpg \$QUIET_ARG --import /nix/store/[0-9a-z]*-key1$'
+ assertFileRegex activate \
+ '^\$DRY_RUN_CMD importTrust "/nix/store/[0-9a-z]*-key1" 1$'
+ assertFileRegex activate \
+ '^\$DRY_RUN_CMD @gnupg@/bin/gpg \$QUIET_ARG --import /nix/store/[0-9a-z]*-key2$'
+ '';
+}
diff --git a/tests/modules/programs/gpg/override-defaults.nix b/tests/modules/programs/gpg/override-defaults.nix
index 3b00e451b..62fe50dc2 100644
--- a/tests/modules/programs/gpg/override-defaults.nix
+++ b/tests/modules/programs/gpg/override-defaults.nix
@@ -23,6 +23,8 @@ with lib;
nmt.script = ''
assertFileExists home-files/bar/foopg/gpg.conf
assertFileContent home-files/bar/foopg/gpg.conf ${./override-defaults-expected.conf}
+
+ assertFileNotRegex activate "^unset GNUPGHOME keyId importTrust$"
'';
};
}