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        # Managed public keys, all ultimately trusted.
 47        publicKeys =
 48          map (key: {
 49            trust = 5;
 50            source =
 51              if builtins.isPath key
 52              then key
 53              else pkgs.writeText "gpg-pubkey" key;
 54          })
 55          user.identity.gpgKeys;
 56
 57        # Build the immutable keyring with the managed keys imported.
 58        gpgKeyring = pkgs.runCommand "gpg-pubring" {buildInputs = [pkgs.gnupg];} (
 59          let
 60            gpg = "${pkgs.gnupg}/bin/gpg";
 61
 62            importKey = {
 63              source,
 64              trust,
 65              ...
 66            }: ''
 67              ${gpg} --import ${source}
 68              ${lib.optionalString (trust != null) ''importTrust "${source}" ${toString trust}''}
 69            '';
 70
 71            importKeys = lib.concatMapStringsSep "\n" importKey publicKeys;
 72          in ''
 73            GNUPGHOME=$(mktemp -d)
 74            export GNUPGHOME
 75
 76            function gpgKeyId() {
 77              ${gpg} --show-key --with-colons "$1" \
 78                | grep ^pub: \
 79                | cut -d: -f5
 80            }
 81
 82            function importTrust() {
 83              local keyIds trust
 84              mapfile -t keyIds <<< "$(gpgKeyId "$1")"
 85              trust="$2"
 86              for id in "''${keyIds[@]}"; do
 87                { echo trust; echo "$trust"; (( trust == 5 )) && echo y; echo quit; } \
 88                  | ${gpg} --no-tty --command-fd 0 --edit-key "$id"
 89              done
 90            }
 91
 92            ${importKeys}
 93
 94            mkdir $out
 95            cp $GNUPGHOME/pubring.kbx $out/pubring.kbx
 96            if [[ -e $GNUPGHOME/trustdb.gpg ]] ; then
 97              cp $GNUPGHOME/trustdb.gpg $out/trustdb.gpg
 98            fi
 99          ''
100        );
101      in {
102        packages = [pkgs.gnupg];
103
104        environment.sessionVariables.GNUPGHOME = "${config.directory}/.gnupg";
105
106        files =
107          {
108            # This configuration is based on the tutorial below, it allows for a robust setup
109            # https://blog.eleven-labs.com/en/openpgp-almost-perfect-key-pair-part-1
110            # ~/.gnupg/gpg.conf
111            ".gnupg/gpg.conf" = {
112              generator = gpgConfGenerator;
113              value = {
114                # Get rid of the copyright notice
115                no-greeting = true;
116
117                # --- Avoid information leaked --- #
118                # Disable inclusion of the version string in ASCII armored output
119                no-emit-version = true;
120                # Do not write comment packets
121                no-comments = false;
122                # Export the smallest key possible
123                # This removes all signatures except the most recent self-signature on each user ID
124                export-options = "export-minimal";
125
126                # Display long key IDs
127                keyid-format = "0xlong";
128                # List all keys (or the specified ones) along with their fingerprints
129                with-fingerprint = true;
130
131                # Display the calculated validity of user IDs during key listings
132                list-options = "show-uid-validity";
133                verify-options = "show-uid-validity show-keyserver-urls";
134
135                # Select the strongest cipher
136                personal-cipher-preferences = "AES256";
137                # Select the strongest digest
138                personal-digest-preferences = "SHA512";
139                # This preference list is used for new keys and becomes the default for "setpref" in the edit menu
140                default-preference-list = "SHA512 SHA384 SHA256 RIPEMD160 AES256 TWOFISH BLOWFISH ZLIB BZIP2 ZIP Uncompressed";
141
142                # Use the strongest cipher algorithm
143                cipher-algo = "AES256";
144                # Use the strongest digest algorithm
145                digest-algo = "SHA512";
146                # Message digest algorithm used when signing a key
147                cert-digest-algo = "SHA512";
148                # Use RFC-1950 ZLIB compression
149                compress-algo = "ZLIB";
150
151                # Disable weak algorithm
152                disable-cipher-algo = "3DES";
153                # Treat the specified digest algorithm as weak
154                weak-digest = "SHA1";
155
156                # The cipher algorithm for symmetric encryption for symmetric encryption with a passphrase
157                s2k-cipher-algo = "AES256";
158                # The digest algorithm used to mangle the passphrases for symmetric encryption
159                s2k-digest-algo = "SHA512";
160                # Selects how passphrases for symmetric encryption are mangled
161                s2k-mode = "3";
162                # Specify how many times the passphrases mangling for symmetric encryption is repeated
163                s2k-count = "65011712";
164              };
165            };
166          }
167          // lib.optionalAttrs (publicKeys != []) {
168            # Immutable keyring: managed keys/trust are linked from the store.
169            ".gnupg/pubring.kbx".source = "${gpgKeyring}/pubring.kbx";
170            ".gnupg/trustdb.gpg" = {
171              type = "copy";
172              source = "${gpgKeyring}/trustdb.gpg";
173            };
174          };
175      };
176    };
177  };
178}