carotte: try to upgrade
[sourcephile-nix.git] / shell / modules / tools / security / gnupg.nix
index 02e3da6074261afbf8d96976250ef14c578969a6..a34d1d980f1624525b5af0c4184a47c0d8639e5d 100644 (file)
@@ -7,220 +7,161 @@ let
 
   generateKeys = keys: unlines (lib.mapAttrsToList generateKey keys);
   generateKey =
-   uid:
-   { uid ? uid
-   , algo ? "future-default"
-   , usage ? ["default"]
-   , expire ? "-"
-   , passPath
-   , subKeys ? {}
-   , ...
-   }@primary:
+    _uid:
+    { uid ? uid
+    , algo ? "future-default"
+    , usage ? [ "default" ]
+    , expire ? "-"
+    , passPath
+    , subKeys ? { }
+    , postRun ? ""
+    , ...
+    }@primary:
     ''
-    info "generateKey uid=\"${uid}\""
-    if ! ${gpg-with-home}/bin/gpg-with-home --list-secret-keys -- "=${uid}" >/dev/null 2>/dev/null
-     then
-      ${pkgs.pass}/bin/pass "${passPath}" |
-      ${gpg-with-home}/bin/gpg-with-home \
-        --batch --pinentry-mode loopback --passphrase-fd 0 \
-        --quick-generate-key "${uid}" "${algo}" "${unwords usage}" "${expire}"
-     fi
-    ${head1}
-    fpr=$(${gpg-fingerprint}/bin/gpg-fingerprint -- "=${uid}" | head1)
-    caps=$(${gpg-with-home}/bin/gpg-with-home \
-            --with-colons --fixed-list-mode --with-fingerprint \
-            --list-secret-keys -- "=${uid}" |
-           ${pkgs.gnugrep}/bin/grep '^ssb:' |
-           ${pkgs.coreutils}/bin/cut -d : -f 12 || true)
+      info "generateKey uid=\"${uid}\""
+      if ! ${gpg-with-home}/bin/gpg-with-home --list-secret-keys -- "=${uid}" >/dev/null 2>/dev/null
+       then
+        ${if passPath != "" then "${pkgs.pass}/bin/pass '${passPath}'" else "cat /dev/null"} |
+        ${gpg-with-home}/bin/gpg-with-home \
+          --batch --pinentry-mode loopback --passphrase-fd 0 \
+          --quick-generate-key "${uid}" "${algo}" "${unwords usage}" "${expire}"
+       fi
+      ${head1}
+      fpr=$(${gpg-fingerprint}/bin/gpg-fingerprint -- "=${uid}" | head1)
+      caps=$(${gpg-with-home}/bin/gpg-with-home \
+              --with-colons --with-fingerprint \
+              --list-secret-keys -- "=${uid}" |
+             ${pkgs.gnugrep}/bin/grep '^ssb:' |
+             ${pkgs.coreutils}/bin/cut -d : -f 12 || true)
     ''
     + unlines (map (generateSubKey primary) subKeys)
     + generateBackupKey "$fpr" primary
-    ;
+    + postRun
+  ;
   generateSubKey =
-   primary:
-   { expire ? primary.expire
-   , algo   ? primary.algo
-   , usage
-   , ...
-   }:
+    primary:
+    { expire ? primary.expire
+    , algo ? primary.algo
+    , usage
+    , ...
+    }:
     ''
-    info "  generateSubKey usage=[${unwords usage}]"
-    if ! printf '%s\n' "$caps" | ${pkgs.gnugrep}/bin/grep -Fqx "${lettersKeyUsage usage}"
-     then
-      ${pkgs.pass}/bin/pass "${primary.passPath}" |
-      ${gpg-with-home}/bin/gpg-with-home \
-        --batch --pinentry-mode loopback --passphrase-fd 0 \
-        --quick-add-key "$fpr" "${algo}" "${unwords usage}" "${expire}"
-     fi
+      info "  generateSubKey usage=[${unwords usage}]"
+      if ! printf '%s\n' "$caps" | ${pkgs.gnugrep}/bin/grep -Fqx "${lettersKeyUsage usage}"
+       then
+        ${if primary.passPath != "" then "${pkgs.pass}/bin/pass '${primary.passPath}'" else "cat /dev/null"} |
+        ${gpg-with-home}/bin/gpg-with-home \
+          --batch --pinentry-mode loopback --passphrase-fd 0 \
+          --quick-add-key "$fpr" "${algo}" "${unwords usage}" "${expire}"
+       fi
     '';
   generateBackupKey =
-   fpr:
-   { passPath
-   , backupRecipients ? []
-   , uid
-   , ...
-   }:
-    lib.optionalString (backupRecipients != [])
-    ''
-    info "  generateBackupKey backupRecipients=[${unwords (map (s: "\\\"${s}\\\"") backupRecipients)}]"
-    mkdir -p "${gnupg.gnupgHome}/backup/${uid}/"
-    if ! test -s "${gnupg.gnupgHome}/backup/${uid}/${fpr}.pubkey.asc"
-     then
-      ${gpg-with-home}/bin/gpg-with-home \
-        --batch \
-        --armor --yes --output "${gnupg.gnupgHome}/backup/${uid}/${fpr}.pubkey.asc" \
-        --export-options export-backup \
-        --export "${fpr}"
-     fi
-    '' + (if backupRecipients == [""] then
-    ''
-    if ! test -s "${gnupg.gnupgHome}/backup/${uid}/${fpr}.revoke.asc"
-     then
-      ${pkgs.pass}/bin/pass "${passPath}" |
-      ${gpg-with-home}/bin/gpg-with-home \
-        --pinentry-mode loopback --passphrase-fd 0 \
-        --armor --yes --output "${gnupg.gnupgHome}/backup/${uid}/${fpr}.revoke.asc" \
-        --gen-revoke "${fpr}"
-     fi
-    if ! test -s "${gnupg.gnupgHome}/backup/${uid}/${fpr}.privkey.sec"
-     then
-      ${pkgs.pass}/bin/pass "${passPath}" |
-      ${gpg-with-home}/bin/gpg-with-home \
-        --batch --pinentry-mode loopback --passphrase-fd 0 \
-        --armor --yes --output "${gnupg.gnupgHome}/backup/${uid}/${fpr}.privkey.sec" \
-        --export-options export-backup \
-        --export-secret-key "${fpr}"
-     fi
-    if ! test -s "${gnupg.gnupgHome}/backup/${uid}/${fpr}.subkeys.sec"
-     then
-      ${pkgs.pass}/bin/pass "${passPath}" |
-      ${gpg-with-home}/bin/gpg-with-home \
-        --batch --pinentry-mode loopback --passphrase-fd 0 \
-        --armor --yes --output "${gnupg.gnupgHome}/backup/${uid}/${fpr}.subkeys.sec" \
-        --export-options export-backup \
-        --export-secret-subkeys "${fpr}"
-     fi
-    '' else ''
-    if ! test -s "${gnupg.gnupgHome}/backup/${uid}/${fpr}.revoke.asc.gpg"
-     then
-      ${pkgs.pass}/bin/pass "${passPath}" |
-      ${gpg-with-home}/bin/gpg-with-home \
-        --pinentry-mode loopback --passphrase-fd 0 \
-        --armor --gen-revoke "${fpr}" |
-      gpg --encrypt ${recipients backupRecipients} \
-          --armor --yes --output "${gnupg.gnupgHome}/backup/${uid}/${fpr}.revoke.asc.gpg"
-     fi
-    if ! test -s "${gnupg.gnupgHome}/backup/${uid}/${fpr}.privkey.sec.gpg"
-     then
-      ${pkgs.pass}/bin/pass "${passPath}" |
-      ${gpg-with-home}/bin/gpg-with-home \
-        --batch --pinentry-mode loopback --passphrase-fd 0 \
-        --armor --export-options export-backup \
-        --export-secret-key "${fpr}" |
-      gpg --encrypt ${recipients backupRecipients} \
-          --armor --yes --output "${gnupg.gnupgHome}/backup/${uid}/${fpr}.privkey.sec.gpg"
-     fi
-    if ! test -s "${gnupg.gnupgHome}/backup/${uid}/${fpr}.subkeys.sec.gpg"
-     then
-      ${pkgs.pass}/bin/pass "${passPath}" |
-      ${gpg-with-home}/bin/gpg-with-home \
-        --batch --pinentry-mode loopback --passphrase-fd 0 \
-        --armor --export-options export-backup \
-        --export-secret-subkeys "${fpr}" |
-      gpg --encrypt ${recipients backupRecipients} \
-          --armor --yes --output "${gnupg.gnupgHome}/backup/${uid}/${fpr}.subkeys.sec.gpg"
-     fi
+    fpr:
+    { passPath
+    , backupRecipients ? [ ]
+    , uid
+    , ...
+    }:
+    lib.optionalString (backupRecipients != [ ])
+      ''
+        info "  generateBackupKey backupRecipients=[${unwords (map (s: "\\\"${s}\\\"") backupRecipients)}]"
+        mkdir -p "${gnupg.gnupgHome}/backup/${uid}/"
+        if ! test -s "${gnupg.gnupgHome}/backup/${uid}/${fpr}.pubkey.asc"
+         then
+          ${gpg-with-home}/bin/gpg-with-home \
+            --batch \
+            --armor --yes --output "${gnupg.gnupgHome}/backup/${uid}/${fpr}.pubkey.asc" \
+            --export-options export-backup \
+            --export "${fpr}"
+         fi
+      '' + (if backupRecipients == [ "" ] then
+      ''
+        if ! test -s "${gnupg.gnupgHome}/backup/${uid}/${fpr}.revoke.asc" &&
+         ${gpg-with-home}/bin/gpg-with-home --list-secret-keys "${fpr}" | grep -q "sec "
+         then
+          ${if passPath != "" then "${pkgs.pass}/bin/pass '${passPath}'" else "cat /dev/null"} |
+          ${gpg-with-home}/bin/gpg-with-home \
+            --pinentry-mode loopback --passphrase-fd 0 \
+            --armor --yes --output "${gnupg.gnupgHome}/backup/${uid}/${fpr}.revoke.asc" \
+            --gen-revoke "${fpr}"
+         fi
+        if ! test -s "${gnupg.gnupgHome}/backup/${uid}/${fpr}.privkey.sec"
+         then
+          ${if passPath != "" then "${pkgs.pass}/bin/pass '${passPath}'" else "cat /dev/null"} |
+          ${gpg-with-home}/bin/gpg-with-home \
+            --batch --pinentry-mode loopback --passphrase-fd 0 \
+            --armor --yes --output "${gnupg.gnupgHome}/backup/${uid}/${fpr}.privkey.sec" \
+            --export-options export-backup \
+            --export-secret-key "${fpr}"
+         fi
+        if ! test -s "${gnupg.gnupgHome}/backup/${uid}/${fpr}.subkeys.sec"
+         then
+          ${if passPath != "" then "${pkgs.pass}/bin/pass '${passPath}'" else "cat /dev/null"} |
+          ${gpg-with-home}/bin/gpg-with-home \
+            --batch --pinentry-mode loopback --passphrase-fd 0 \
+            --armor --yes --output "${gnupg.gnupgHome}/backup/${uid}/${fpr}.subkeys.sec" \
+            --export-options export-backup \
+            --export-secret-subkeys "${fpr}"
+         fi
+      '' else ''
+      if ! test -s "${gnupg.gnupgHome}/backup/${uid}/${fpr}.revoke.asc.gpg"
+       then
+        ${if passPath != "" then "${pkgs.pass}/bin/pass '${passPath}'" else "cat /dev/null"} |
+        ${gpg-with-home}/bin/gpg-with-home \
+          --pinentry-mode loopback --passphrase-fd 0 \
+          --armor --gen-revoke "${fpr}" |
+        gpg --encrypt ${recipients backupRecipients} \
+            --armor --yes --output "${gnupg.gnupgHome}/backup/${uid}/${fpr}.revoke.asc.gpg"
+       fi
+      if ! test -s "${gnupg.gnupgHome}/backup/${uid}/${fpr}.privkey.sec.gpg"
+       then
+        ${if passPath != "" then "${pkgs.pass}/bin/pass '${passPath}'" else "cat /dev/null"} |
+        ${gpg-with-home}/bin/gpg-with-home \
+          --batch --pinentry-mode loopback --passphrase-fd 0 \
+          --armor --export-options export-backup \
+          --export-secret-key "${fpr}" |
+        gpg --encrypt ${recipients backupRecipients} \
+            --armor --yes --output "${gnupg.gnupgHome}/backup/${uid}/${fpr}.privkey.sec.gpg"
+       fi
+      if ! test -s "${gnupg.gnupgHome}/backup/${uid}/${fpr}.subkeys.sec.gpg"
+       then
+        ${if passPath != "" then "${pkgs.pass}/bin/pass '${passPath}'" else "cat /dev/null"} |
+        ${gpg-with-home}/bin/gpg-with-home \
+          --batch --pinentry-mode loopback --passphrase-fd 0 \
+          --armor --export-options export-backup \
+          --export-secret-subkeys "${fpr}" |
+        gpg --encrypt ${recipients backupRecipients} \
+            --armor --yes --output "${gnupg.gnupgHome}/backup/${uid}/${fpr}.subkeys.sec.gpg"
+       fi
     '');
   recipients = rs: unwords (map (r: ''--recipient "${refKey r}"'') rs);
   refKey = key: if builtins.typeOf key == "string" then key else "=${key.uid}";
-  signer = s: if s == null
-              then ""
-              else ''--sign --default-key "${refKey s}"'';
   lettersKeyUsage = usage:
     (if builtins.elem "encrypt" usage then "e" else "") +
-    (if builtins.elem "sign"    usage then "s" else "") +
-    (if builtins.elem "cert"    usage then "c" else "") +
-    (if builtins.elem "auth"    usage then "a" else "");
-
-  passOfFingerprint = key:
-    # Return shell code
-    # which fills a map from the fingerprints of the given key
-    # to its password file.
-    ''
-    # shell.gnupg.pass.passOfFingerprint
-    for fpr in $(${gpg-fingerprint}/bin/gpg-fingerprint -- "=${key.uid}")
-     do eval "pass_$fpr=\"${key.passPath}\""
-     done
-    '';
-  forgetPass =
-    # Return shell code
-    # which installs an exit and keyboard interruption (^C) trap
-    # removing any pass from gpg-agent
-    # whose keygrip is registered in $keygrips.
-    ''
-    # forgetPass
-    keygrips=
-    forgetPass () {
-      for keygrip in $keygrips
-       do
-        echo >&2 "gpg: forget: keygrip=$keygrip"
-        GNUPGHOME=${gnupg.gnupgHome} \
-        ${pkgs.gnupg}/bin/gpg-connect-agent </dev/null >&2 "CLEAR_PASSPHRASE $keygrip" ||
-        true
-       done
-      keygrips=
-     }
-    trap 'forgetPass' EXIT INT
-    '';
-  presetPass = keys: uid:
-    # Return shell code
-    # which preset the pass of given uid into gpg-agent,
-    # using keys to find where the pass is stored.
-    ''
-    ${unlines (map passOfFingerprint keys)}
-    # presetPass
-    GNUPGHOME=${gnupg.gnupgHome} \
-    ${pkgs.gnupg}/bin/gpgconf --launch gpg-agent
-    ${head1}
-    fpr="$(${gpg-fingerprint}/bin/fingerprint -- "${uid}" | head1)"
-    eval pass="\''${pass_$fpr}"
-    if test -n "$pass"
-     then
-      for keygrip in $(${gnupg.gpg-keygrip}/bin/gpg-keygrip -- "$fpr")
-       do
-        keygrips="$keygrips $keygrip"
-        echo >&2 "gpg: preset: keygrip=$keygrip pass=$pass"
-        ${pkgs.pass}/bin/pass "$pass" |
-        GNUPGHOME=${gnupg.gnupgHome} \
-        ${pkgs.gnupg}/libexec/gpg-preset-passphrase --preset ''${XTRACE:+--verbose} $keygrip
-       done
-     fi
-    '';
+    (if builtins.elem "sign" usage then "s" else "") +
+    (if builtins.elem "cert" usage then "c" else "") +
+    (if builtins.elem "auth" usage then "a" else "");
 
   # Initialize the keyring according to gnupg.keys.
   gpg-init = pkgs.writeShellScriptBin "gpg-init" (''
     set -eu
     set -o pipefail
     ${info}
-    ${pkgs.coreutils}/bin/install -dm0700 -D ${gnupg.gnupgHome}
-    ${pkgs.coreutils}/bin/ln -snf ${gnupg.gpgConf}      ${gnupg.gnupgHome}/gpg.conf
-    ${pkgs.coreutils}/bin/ln -snf ${gnupg.gpgAgentConf} ${gnupg.gnupgHome}/gpg-agent.conf
-    ${pkgs.coreutils}/bin/ln -snf ${gnupg.dirmngrConf}  ${gnupg.gnupgHome}/dirmngr.conf
-    '' +
-    generateKeys gnupg.keys
+  '' +
+  generateKeys gnupg.keys
   );
 
   # A wrapper around gpg to set GNUPGHOME.
   gpg-with-home = pkgs.writeScriptBin "gpg-with-home" ''
     GNUPGHOME=${gnupg.gnupgHome} \
     exec ${pkgs.gnupg}/bin/gpg "$@"
-    '';
+  '';
 
   # A wrapper around gpg to get fingerprints.
   gpg-fingerprint = pkgs.writeScriptBin "gpg-fingerprint" ''
     set -eu
     ${gpg-with-home}/bin/gpg-with-home \
-     --with-colons --fixed-list-mode --with-fingerprint --with-subkey-fingerprint \
+     --with-colons --with-fingerprint --with-subkey-fingerprint \
      --list-public-keys "$@" |
     while IFS=: read -r t x x x key x x x x uid x
      do case $t in
@@ -230,29 +171,22 @@ let
          esac done
         ;;
      esac done
-    '';
+  '';
 
   # A wrapper around gpg to get keygrips.
   gpg-keygrip = pkgs.writeScriptBin "gpg-keygrip" ''
     set -eu
     ${gpg-with-home}/bin/gpg-with-home \
-     --with-colons --fixed-list-mode --with-keygrip \
+     --with-colons --with-keygrip \
      --list-public-keys "$@" |
-    while IFS=: read -r t x x x key x x x x uid x
-     do case $t in
-       (pub|sub|sec|ssb)
-        while IFS=: read -r t x x x x x x x x grp x
-         do case $t in (grp) printf '%s\n' "$grp"; break;;
-         esac done
-        ;;
-     esac done
-    '';
+    while IFS=: read -r t x x x key x x x x uid x do case $t in (pub|sub|sec|ssb) while IFS=: read -r t x x x x x x x x grp x do case $t in (grp) printf '%s\n' "$grp"; break;; esac done ;; esac done
+  '';
 
   # A wrapper around gpg to get uids.
   gpg-uid = pkgs.writeScriptBin "gpg-uid" ''
     set -eu
     ${gpg-with-home}/bin/gpg-with-home \
-     --with-colons --fixed-list-mode \
+     --with-colons \
      --list-public-keys "$@" |
     while IFS=: read -r t st x x x x x id x uid x
      do case $t in
@@ -278,198 +212,242 @@ let
   '';
 in
 {
-options.gnupg = {
-  enable = lib.mkEnableOption "GnuPG shell utilities";
-  gnupgHome = lib.mkOption {
-    type = types.path;
-    default = "sec/gnupg";
-    description = ''
+  options.gnupg = {
+    enable = lib.mkEnableOption "GnuPG shell utilities";
+    gnupgHome = lib.mkOption {
+      type = types.str;
+      default = "sec/gnupg";
+      description = ''
     '';
-  };
-  keys = lib.mkOption {
-    default = {};
-    example =
-      { "John Doe. <contact@example.coop>" = {
-          algo   = "rsa4096";
-          expire = "1y";
-          usage  = ["cert" "sign"];
-          passPath = "example.coop/gpg/contact";
-          subKeys = [
-            { algo = "rsa4096"; expire = "1y"; usage = ["sign"];}
-            { algo = "rsa4096"; expire = "1y"; usage = ["encrypt"];}
-            { algo = "rsa4096"; expire = "1y"; usage = ["auth"];}
+    };
+    keys = lib.mkOption {
+      default = { };
+      example =
+        {
+          "John Doe. <contact@example.coop>" = {
+            algo = "rsa4096";
+            expire = "1y";
+            usage = [ "cert" "sign" ];
+            passPath = "example.coop/gpg/contact";
+            subKeys = [
+              { algo = "rsa4096"; expire = "1y"; usage = [ "sign" ]; }
+              { algo = "rsa4096"; expire = "1y"; usage = [ "encrypt" ]; }
+              { algo = "rsa4096"; expire = "1y"; usage = [ "auth" ]; }
             ];
-          backupRecipients = ["@john@doe.pro"];
+            backupRecipients = [ "@john@doe.pro" ];
+          };
         };
-      };
-    type = types.attrsOf (types.submodule ({uid, ...}: {
-      #config.uid = lib.mkDefault uid;
-      options = {
-        uid = lib.mkOption {
-          type        = types.str;
-          example     = "John Doe <john.doe@example.coop>";
-          default     = uid;
-          description = ''
-            User ID.
-          '';
-        };
-        algo = lib.mkOption {
-          type        = types.enum [ "rsa4096" ];
-          default     = "future-default";
-          example     = "rsa4096";
-          description = ''
-            Cryptographic algorithm.
-          '';
-        };
-        expire = lib.mkOption {
-          type        = types.str;
-          default     = "1y";
-          example     = "1y";
-          description = ''
-            Expiration timeout.
-          '';
-        };
-        usage = lib.mkOption {
-          type        = with types; listOf (enum [ "cert" "sign" "encrypt" "auth" "default" ]);
-          default     = ["default"];
-          example     = ["cert" "sign" "encrypt" "auth"];
-          description = ''
-            Cryptographic usage.
-          '';
-        };
-        passPath = lib.mkOption {
-          type        = types.str;
-          example     = "gnupg/coop/example/contact@";
-          description = ''
-            Password path.
-          '';
-        };
-        subKeys = lib.mkOption {
-          type = types.listOf (types.submodule {
-            options = {
-              algo = lib.mkOption {
-                type        = types.enum [ "rsa4096" ];
-                default     = "default";
-                example     = "rsa4096";
-                description = ''
-                  Cryptographic algorithm.
-                '';
-              };
-              expire = lib.mkOption {
-                type        = types.str;
-                default     = "1y";
-                example     = "1y";
-                description = ''
-                  Expiration timeout.
-                '';
-              };
-              usage = lib.mkOption {
-                type        = with types; listOf (enum [ "sign" "encrypt" "auth" "default" ]);
-                default     = ["default"];
-                example     = ["sign" "encrypt" "auth"];
-                description = ''
-                  Cryptographic usage.
-                '';
+      type = types.attrsOf (types.submodule ({ name, ... }: {
+        options = {
+          uid = lib.mkOption {
+            type = types.str;
+            example = "John Doe <john.doe@example.coop>";
+            default = name;
+            description = ''
+              User ID.
+            '';
+          };
+          algo = lib.mkOption {
+            type = types.enum [ "rsa4096" ];
+            default = "future-default";
+            example = "rsa4096";
+            description = ''
+              Cryptographic algorithm.
+            '';
+          };
+          expire = lib.mkOption {
+            type = types.str;
+            default = "0";
+            example = "1y";
+            description = ''
+              Expiration timeout.
+            '';
+          };
+          usage = lib.mkOption {
+            type = with types; listOf (enum [ "cert" "sign" "encrypt" "auth" "default" ]);
+            default = [ "default" ];
+            example = [ "cert" "sign" "encrypt" "auth" ];
+            description = ''
+              Cryptographic usage.
+            '';
+          };
+          passPath = lib.mkOption {
+            type = types.str;
+            example = "gnupg/coop/example/contact@";
+            description = ''
+              Password path.
+            '';
+          };
+          subKeys = lib.mkOption {
+            type = types.listOf (types.submodule {
+              options = {
+                algo = lib.mkOption {
+                  type = types.enum [ "rsa4096" ];
+                  default = "default";
+                  example = "rsa4096";
+                  description = ''
+                    Cryptographic algorithm.
+                  '';
+                };
+                expire = lib.mkOption {
+                  type = types.str;
+                  default = "0";
+                  example = "1y";
+                  description = ''
+                    Expiration timeout.
+                  '';
+                };
+                usage = lib.mkOption {
+                  type = with types; listOf (enum [ "sign" "encrypt" "auth" "default" ]);
+                  default = [ "default" ];
+                  example = [ "sign" "encrypt" "auth" ];
+                  description = ''
+                    Cryptographic usage.
+                  '';
+                };
               };
-            };
-          });
+            });
+          };
+          backupRecipients = lib.mkOption {
+            type = with types; listOf str;
+            default = [ ];
+            example = [ "@john@doe.pro" ];
+            description = ''
+              Backup keys used to encrypt the a backup copy of the secret keys.
+            '';
+          };
+          postRun = lib.mkOption {
+            type = types.lines;
+            default = "";
+            description = ''
+              Shell code to run after the key has been generated or tested to exist.
+            '';
+          };
         };
-        backupRecipients = lib.mkOption {
-          type        = with types; listOf str;
-          default     = [];
-          example     = ["@john@doe.pro"];
-          description = ''
-            Backup keys used to encrypt the a backup copy of the secret keys.
+      }));
+    };
+    dirmngrConf = lib.mkOption {
+      type = types.lines;
+      apply = s: pkgs.writeText "dirmngr.conf" s;
+      default = ''
+        allow-ocsp
+        hkp-cacert ${gnupg.keyserverPEM}
+        keyserver hkps://keys.mayfirst.org
+        #use-tor
+        #log-file ${gnupg.gnupgHome}/dirmngr.log
+        #standard-resolver
+      '';
+      description = ''
+        GnuPG's dirmngr.conf content.
+      '';
+    };
+    keyserverPEM = lib.mkOption {
+      type = types.lines;
+      apply = s: pkgs.writeText "keyserver.pem" s;
+      default = builtins.readFile gnupg/keyserver.pem;
+      description = ''
+        dirmngr's hkp-cacert content.
+      '';
+    };
+    gpgAgentConf = lib.mkOption {
+      type = types.lines;
+      apply = s: pkgs.writeText "gpg-agent.conf" (s + "\n" + gnupg.gpgAgentExtraConf);
+      default =
+        let
+          pinentry = pkgs.writeShellScript "pinentry" ''
+            #!${pkgs.runtimeShell}
+            # choose pinentry depending on PINENTRY_USER_DATA
+            # this *only works* with gpg2
+            # see https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=802020
+            case "''${PINENTRY_USER_DATA:-curses}" in
+            curses) exec ${pkgs.pinentry.curses}/bin/pinentry-curses "$@";;
+            #emacs)  exec ''${pkgs.pinentry.emacs}/bin/pinentry-emacs "$@";;
+            #gnome3) exec ''${pkgs.pinentry.gnome3}/bin/pinentry-gnome3 "$@";;
+            gtk-2)  exec ''${pkgs.pinentry.gtk2}/bin/pinentry-gtk-2 "$@";;
+            none)   exit 1;; # do not ask for passphrase
+            #qt)     exec ''${pkgs.pinentry.qt}/bin/pinentry-qt "$@";;
+            tty)    exec ${pkgs.pinentry.tty}/bin/pinentry-tty "$@";;
+            esac
           '';
-        };
-      };
-    }));
-  };
-  dirmngrConf = lib.mkOption {
-    type = types.lines;
-    apply = s: pkgs.writeText "dirmngr.conf" s;
-    default = ''
-      allow-ocsp
-      hkp-cacert ${gnupg.keyserverPEM}
-      keyserver hkps://keys.mayfirst.org
-      use-tor
-      #log-file ${gnupg.gnupgHome}/dirmngr.log
-      #standard-resolver
-    '';
-    description = ''
-      GnuPG's dirmngr.conf content.
-    '';
+        in
+        ''
+          allow-loopback-pinentry
+          allow-preset-passphrase
+          default-cache-ttl 17200
+          default-cache-ttl-ssh 17200
+          enable-ssh-support
+          max-cache-ttl 17200
+          max-cache-ttl-ssh 17200
+          no-allow-external-cache
+          pinentry-program ${pinentry}
+        '';
+      description = ''
+        GnuPG's gpg-agent.conf content.
+      '';
+    };
+    gpgConf = lib.mkOption {
+      type = types.lines;
+      apply = s: pkgs.writeText "gpg.conf" (s + "\n" + gnupg.gpgExtraConf);
+      default = ''
+        auto-key-locate keyserver
+        cert-digest-algo SHA512
+        charset utf-8
+        default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 TWOFISH BZIP2 ZLIB ZIP Uncompressed
+        keyid-format 0xlong
+        keyserver-options no-honor-keyserver-url
+        no-auto-key-locate
+        no-default-keyring
+        no-emit-version
+        personal-cipher-preferences AES256 AES CAST5
+        personal-digest-preferences SHA512
+        quiet
+        s2k-cipher-algo AES256
+        s2k-count 65536
+        s2k-digest-algo SHA512
+        s2k-mode 3
+        tofu-default-policy unknown
+        trust-model tofu+pgp
+        use-agent
+        utf8-strings
+      '';
+      description = ''
+        GnuPG's gpg.conf content.
+      '';
+    };
+    gpgExtraConf = lib.mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        GnuPG's gpg.conf extra content.
+      '';
+    };
+    gpgAgentExtraConf = lib.mkOption {
+      type = types.lines;
+      default = "";
+      description = ''
+        GnuPG's gpg-agent.conf extra content.
+      '';
+    };
   };
-  keyserverPEM = lib.mkOption {
-    type = types.lines;
-    apply = s: pkgs.writeText "keyserver.pem" s;
-    default = builtins.readFile gnupg/keyserver.pem;
-    description = ''
-      dirmngr's hkp-cacert content.
+  config = lib.mkIf gnupg.enable {
+    nix-shell.buildInputs = [
+      gpg-with-home
+      gpg-fingerprint
+      gpg-keygrip
+      gpg-uid
+      gpg-init
+    ];
+    nix-shell.shellHook = ''
+      # gnupg
+      ${pkgs.coreutils}/bin/install -dm0700 -D ${gnupg.gnupgHome}
+      ${pkgs.coreutils}/bin/ln -snf ${gnupg.gpgConf}      ${gnupg.gnupgHome}/gpg.conf
+      ${pkgs.coreutils}/bin/ln -snf ${gnupg.gpgAgentConf} ${gnupg.gnupgHome}/gpg-agent.conf
+      ${pkgs.coreutils}/bin/ln -snf ${gnupg.dirmngrConf}  ${gnupg.gnupgHome}/dirmngr.conf
+      export GNUPGHOME=${gnupg.gnupgHome}
+      install -dm700 "$GNUPGHOME"
+      export GPG_TTY=$(${pkgs.coreutils}/bin/tty)
+      ${pkgs.gnupg}/bin/gpgconf --launch gpg-agent
+      export SSH_AUTH_SOCK=$(${pkgs.gnupg}/bin/gpgconf --list-dirs agent-ssh-socket)
     '';
   };
-  gpgAgentConf = lib.mkOption {
-    type = types.lines;
-    apply = s: pkgs.writeText "gpg-agent.conf" s;
-    default = ''
-      allow-preset-passphrase
-      default-cache-ttl 17200
-      default-cache-ttl-ssh 17200
-      enable-ssh-support
-      max-cache-ttl 17200
-      max-cache-ttl-ssh 17200
-      pinentry-program ${pkgs.pinentry}/bin/pinentry
-    '';
-    description = ''
-      GnuPG's gpg-agent.conf content.
-    '';
-  };
-  gpgConf = lib.mkOption {
-    type = types.lines;
-    apply = s: pkgs.writeText "gpg.conf" s;
-    default = ''
-      auto-key-locate keyserver
-      cert-digest-algo SHA512
-      charset utf-8
-      default-preference-list SHA512 SHA384 SHA256 SHA224 AES256 AES192 AES CAST5 TWOFISH BZIP2 ZLIB ZIP Uncompressed
-      fixed-list-mode
-      keyid-format 0xlong
-      keyserver-options no-honor-keyserver-url
-      no-auto-key-locate
-      no-default-keyring
-      no-emit-version
-      personal-cipher-preferences AES256 AES CAST5
-      personal-digest-preferences SHA512
-      quiet
-      s2k-cipher-algo AES256
-      s2k-count 65536
-      s2k-digest-algo SHA512
-      s2k-mode 3
-      tofu-default-policy unknown
-      trust-model tofu+pgp
-      use-agent
-      utf8-strings
-    '';
-    description = ''
-      GnuPG's gpg.conf content.
-    '';
-  };
-};
-config = lib.mkIf gnupg.enable {
-  nix-shell.buildInputs = [
-    gpg-with-home
-    gpg-fingerprint
-    gpg-keygrip
-    gpg-uid
-    gpg-init
-  ];
-  nix-shell.shellHook = ''
-    # gnupg
-    export GNUPGHOME=${gnupg.gnupgHome}
-    install -dm700 "$GNUPGHOME"
-    export GPG_TTY=$(${pkgs.coreutils}/bin/tty)
-    ${pkgs.gnupg}/bin/gpgconf --launch gpg-agent
-    export SSH_AUTH_SOCK=$(${pkgs.gnupg}/bin/gpgconf --list-dirs agent-ssh-socket)
-  '';
-};
 }