gpg: support declarative trust and public keys
PR #810
(cherry picked from commit ea1794a798
)
This commit is contained in:
parent
93ad8661c7
commit
e0bfb57d62
|
@ -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 = {
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
tests/modules/programs/gpg/immutable-keyfiles.nix
Normal file
52
tests/modules/programs/gpg/immutable-keyfiles.nix
Normal file
|
@ -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
tests/modules/programs/gpg/mutable-keyfiles.nix
Normal file
30
tests/modules/programs/gpg/mutable-keyfiles.nix
Normal file
|
@ -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$'
|
||||
'';
|
||||
}
|
|
@ -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$"
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue