Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 additions & 0 deletions modules/programs/gpg.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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
<variablelist>
<varlistentry>
<term><literal>1</literal></term>
<listitem><para>I don't know or won't say.</para></listitem>
</varlistentry>
<varlistentry>
<term><literal>2</literal></term>
<listitem><para>I do NOT trust.</para></listitem>
</varlistentry>
<varlistentry>
<term><literal>3</literal></term>
<listitem><para>I trust marginally.</para></listitem>
</varlistentry>
<varlistentry>
<term><literal>4</literal></term>
<listitem><para>I trust fully.</para></listitem>
</varlistentry>
<varlistentry>
<term><literal>5</literal></term>
<listitem><para>I trust ultimately.</para></listitem>
</varlistentry>
</variablelist>
</para><para>
See <link xlink:href="https://www.gnupg.org/gph/en/manual/x334.html"/>
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 = {
Expand Down Expand Up @@ -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 <literal>true</literal>, you may manage your keyring as a user
using the <literal>gpg</literal> command. Upon activation, the keyring
will have managed keys added without overwriting unmanaged keys.
</para><para>
If set to <literal>false</literal>, the path
<filename>$GNUPGHOME/pubring.kbx</filename> will become an immutable
link to the Nix store, denying modifications.
'';
};

mutableTrust = mkOption {
type = types.bool;
default = true;
description = ''
If set to <literal>true</literal>, you may manage trust as a user using
the <command>gpg</command> command. Upon activation, trusted keys have
their trust set without overwriting unmanaged keys.
</para><para>
If set to <literal>false</literal>, the path
<filename>$GNUPGHOME/trustdb.gpg</filename> will be
<emphasis>overwritten</emphasis> on each activation, removing trust for
any unmanaged keys. Be careful to make a backup of your old
<filename>trustdb.gpg</filename> 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 {
Expand Down Expand Up @@ -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;
};
};
}
6 changes: 5 additions & 1 deletion tests/modules/programs/gpg/default.nix
Original file line number Diff line number Diff line change
@@ -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;
}
52 changes: 52 additions & 0 deletions tests/modules/programs/gpg/immutable-keyfiles.nix
Original file line number Diff line number Diff line change
@@ -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:$'
'';
}
30 changes: 30 additions & 0 deletions tests/modules/programs/gpg/mutable-keyfiles.nix
Original file line number Diff line number Diff line change
@@ -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$'
'';
}
2 changes: 2 additions & 0 deletions tests/modules/programs/gpg/override-defaults.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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$"
'';
};
}