main
  1{den, ...}: {
  2  den.aspects.desktop.includes = [den.aspects.desktop.gnupg];
  3  den.aspects.desktop.gnupg = {
  4    nixos = {pkgs, ...}: {
  5      programs.gnupg.agent = {
  6        enable = true;
  7        pinentryPackage = pkgs.pinentry-gnome3;
  8        enableSSHSupport = false;
  9        settings.default-cache-ttl = 4 * 60 * 60; # 4 hours
 10      };
 11    };
 12
 13    provides.to-users = {
 14      includes = [
 15        {
 16          persistHome = {config, ...}: {
 17            directories = [
 18              {
 19                directory = config.environment.sessionVariables.GNUPGHOME;
 20                mode = "0700";
 21              }
 22            ];
 23          };
 24        }
 25      ];
 26
 27      hjem = {
 28        user,
 29        pkgs,
 30        lib,
 31        config,
 32        ...
 33      }: let
 34        # Port of home-manager's programs.gpg
 35        # See https://github.com/nix-community/home-manager/blob/c53d643b3737e2fcd04e6cb3b3580ef50b2087a0/modules/programs/gpg.nix
 36        gpgConfGenerator = value:
 37          lib.generators.toKeyValue {
 38            mkKeyValue = key: v:
 39              if lib.isString v
 40              then "${key} ${v}"
 41              else lib.optionalString v key;
 42            listsAsDuplicateKeys = true;
 43          }
 44          value;
 45
 46        publicKeys =
 47          (map (key: {
 48              trust = 5;
 49              source =
 50                if builtins.isPath key
 51                then key
 52                else pkgs.writeText "gpg-pubkey" key;
 53            })
 54            user.identity.gpgKeys)
 55          ++ [
 56          ];
 57
 58        gpg = "${pkgs.gnupg}/bin/gpg";
 59
 60        # Mirror home-manager's mutableKeys/mutableTrust: import managed keys
 61        # into the live keyring instead of linking a store keyring.
 62        importTrustBashFunctions = ''
 63          function gpgKeyId() {
 64            ${gpg} --show-key --with-colons "$1" \
 65              | grep ^pub: \
 66              | cut -d: -f5
 67          }
 68
 69          function importTrust() {
 70            local keyIds trust
 71            mapfile -t keyIds <<< "$(gpgKeyId "$1")"
 72            trust="$2"
 73            for id in "''${keyIds[@]}"; do
 74              { echo trust; echo "$trust"; (( trust == 5 )) && echo y; echo quit; } \
 75                | ${gpg} --no-tty --command-fd 0 --edit-key "$id"
 76            done
 77          }
 78        '';
 79
 80        importKey = {
 81          source,
 82          trust,
 83          ...
 84        }: ''
 85          ${gpg} --quiet --import ${source}
 86          ${lib.optionalString (trust != null) ''importTrust "${source}" ${toString trust}''}
 87        '';
 88
 89        importKeys = lib.concatMapStringsSep "\n" importKey publicKeys;
 90
 91        gnupgHome = "${config.directory}/.gnupg";
 92      in {
 93        packages = [pkgs.gnupg];
 94
 95        environment.sessionVariables.GNUPGHOME = gnupgHome;
 96
 97        systemd.services = lib.optionalAttrs (publicKeys != []) {
 98          gpg-import-keys = {
 99            description = "Import managed GnuPG public keys into the mutable keyring";
100
101            wantedBy = ["default.target"];
102            restartTriggers = map (key: key.source) publicKeys;
103            path = [pkgs.gnupg pkgs.coreutils];
104
105            serviceConfig = {
106              Type = "oneshot";
107              RemainAfterExit = true;
108            };
109
110            script = ''
111              GNUPGHOME=${lib.escapeShellArg gnupgHome}
112              export GNUPGHOME
113              mkdir -m 0700 -p "$GNUPGHOME"
114
115              ${importTrustBashFunctions}
116              ${importKeys}
117            '';
118          };
119        };
120
121        files = {
122          # Based on https://blog.eleven-labs.com/en/openpgp-almost-perfect-key-pair-part-1
123          ".gnupg/gpg.conf" = {
124            generator = gpgConfGenerator;
125            value = {
126              # Get rid of the copyright notice
127              no-greeting = true;
128
129              # --- Avoid information leaked --- #
130              # Disable inclusion of the version string in ASCII armored output
131              no-emit-version = true;
132              # Do not write comment packets
133              no-comments = false;
134              # Export the smallest key possible
135              # This removes all signatures except the most recent self-signature on each user ID
136              export-options = "export-minimal";
137
138              # Display long key IDs
139              keyid-format = "0xlong";
140              # List all keys (or the specified ones) along with their fingerprints
141              with-fingerprint = true;
142
143              # Display the calculated validity of user IDs during key listings
144              list-options = "show-uid-validity";
145              verify-options = "show-uid-validity show-keyserver-urls";
146
147              # Select the strongest cipher
148              personal-cipher-preferences = "AES256";
149              # Select the strongest digest
150              personal-digest-preferences = "SHA512";
151              # This preference list is used for new keys and becomes the default for "setpref" in the edit menu
152              default-preference-list = "SHA512 SHA384 SHA256 RIPEMD160 AES256 TWOFISH BLOWFISH ZLIB BZIP2 ZIP Uncompressed";
153
154              # Use the strongest cipher algorithm
155              cipher-algo = "AES256";
156              # Use the strongest digest algorithm
157              digest-algo = "SHA512";
158              # Message digest algorithm used when signing a key
159              cert-digest-algo = "SHA512";
160              # Use RFC-1950 ZLIB compression
161              compress-algo = "ZLIB";
162
163              # Disable weak algorithm
164              disable-cipher-algo = "3DES";
165              # Treat the specified digest algorithm as weak
166              weak-digest = "SHA1";
167
168              # The cipher algorithm for symmetric encryption for symmetric encryption with a passphrase
169              s2k-cipher-algo = "AES256";
170              # The digest algorithm used to mangle the passphrases for symmetric encryption
171              s2k-digest-algo = "SHA512";
172              # Selects how passphrases for symmetric encryption are mangled
173              s2k-mode = "3";
174              # Specify how many times the passphrases mangling for symmetric encryption is repeated
175              s2k-count = "65011712";
176            };
177          };
178        };
179      };
180    };
181  };
182}