Commit 5dd83b4

HPCesia <me@hpcesia.com>
2026-10-04 20:59:13
gnupg: mutable keys
1 parent 3c8a8de
Changed files (1)
modules
desktop
modules/desktop/gnupg.nix
@@ -43,136 +43,141 @@
           }
           value;
 
-        # Managed public keys, all ultimately trusted.
         publicKeys =
-          map (key: {
-            trust = 5;
-            source =
-              if builtins.isPath key
-              then key
-              else pkgs.writeText "gpg-pubkey" key;
-          })
-          user.identity.gpgKeys;
-
-        # Build the immutable keyring with the managed keys imported.
-        gpgKeyring = pkgs.runCommand "gpg-pubring" {buildInputs = [pkgs.gnupg];} (
-          let
-            gpg = "${pkgs.gnupg}/bin/gpg";
-
-            importKey = {
-              source,
-              trust,
-              ...
-            }: ''
-              ${gpg} --import ${source}
-              ${lib.optionalString (trust != null) ''importTrust "${source}" ${toString trust}''}
-            '';
+          (map (key: {
+              trust = 5;
+              source =
+                if builtins.isPath key
+                then key
+                else pkgs.writeText "gpg-pubkey" key;
+            })
+            user.identity.gpgKeys)
+          ++ [
+          ];
+
+        gpg = "${pkgs.gnupg}/bin/gpg";
+
+        # Mirror home-manager's mutableKeys/mutableTrust: import managed keys
+        # into the live keyring instead of linking a store keyring.
+        importTrustBashFunctions = ''
+          function gpgKeyId() {
+            ${gpg} --show-key --with-colons "$1" \
+              | grep ^pub: \
+              | cut -d: -f5
+          }
+
+          function importTrust() {
+            local keyIds trust
+            mapfile -t keyIds <<< "$(gpgKeyId "$1")"
+            trust="$2"
+            for id in "''${keyIds[@]}"; do
+              { echo trust; echo "$trust"; (( trust == 5 )) && echo y; echo quit; } \
+                | ${gpg} --no-tty --command-fd 0 --edit-key "$id"
+            done
+          }
+        '';
 
-            importKeys = lib.concatMapStringsSep "\n" importKey publicKeys;
-          in ''
-            GNUPGHOME=$(mktemp -d)
-            export GNUPGHOME
-
-            function gpgKeyId() {
-              ${gpg} --show-key --with-colons "$1" \
-                | grep ^pub: \
-                | cut -d: -f5
-            }
-
-            function importTrust() {
-              local keyIds trust
-              mapfile -t keyIds <<< "$(gpgKeyId "$1")"
-              trust="$2"
-              for id in "''${keyIds[@]}"; do
-                { echo trust; echo "$trust"; (( trust == 5 )) && echo y; echo quit; } \
-                  | ${gpg} --no-tty --command-fd 0 --edit-key "$id"
-              done
-            }
-
-            ${importKeys}
-
-            mkdir $out
-            cp $GNUPGHOME/pubring.kbx $out/pubring.kbx
-            if [[ -e $GNUPGHOME/trustdb.gpg ]] ; then
-              cp $GNUPGHOME/trustdb.gpg $out/trustdb.gpg
-            fi
-          ''
-        );
+        importKey = {
+          source,
+          trust,
+          ...
+        }: ''
+          ${gpg} --quiet --import ${source}
+          ${lib.optionalString (trust != null) ''importTrust "${source}" ${toString trust}''}
+        '';
+
+        importKeys = lib.concatMapStringsSep "\n" importKey publicKeys;
+
+        gnupgHome = "${config.directory}/.gnupg";
       in {
         packages = [pkgs.gnupg];
 
-        environment.sessionVariables.GNUPGHOME = "${config.directory}/.gnupg";
-
-        files =
-          {
-            # This configuration is based on the tutorial below, it allows for a robust setup
-            # https://blog.eleven-labs.com/en/openpgp-almost-perfect-key-pair-part-1
-            # ~/.gnupg/gpg.conf
-            ".gnupg/gpg.conf" = {
-              generator = gpgConfGenerator;
-              value = {
-                # Get rid of the copyright notice
-                no-greeting = true;
-
-                # --- Avoid information leaked --- #
-                # Disable inclusion of the version string in ASCII armored output
-                no-emit-version = true;
-                # Do not write comment packets
-                no-comments = false;
-                # Export the smallest key possible
-                # This removes all signatures except the most recent self-signature on each user ID
-                export-options = "export-minimal";
-
-                # Display long key IDs
-                keyid-format = "0xlong";
-                # List all keys (or the specified ones) along with their fingerprints
-                with-fingerprint = true;
-
-                # Display the calculated validity of user IDs during key listings
-                list-options = "show-uid-validity";
-                verify-options = "show-uid-validity show-keyserver-urls";
-
-                # Select the strongest cipher
-                personal-cipher-preferences = "AES256";
-                # Select the strongest digest
-                personal-digest-preferences = "SHA512";
-                # This preference list is used for new keys and becomes the default for "setpref" in the edit menu
-                default-preference-list = "SHA512 SHA384 SHA256 RIPEMD160 AES256 TWOFISH BLOWFISH ZLIB BZIP2 ZIP Uncompressed";
-
-                # Use the strongest cipher algorithm
-                cipher-algo = "AES256";
-                # Use the strongest digest algorithm
-                digest-algo = "SHA512";
-                # Message digest algorithm used when signing a key
-                cert-digest-algo = "SHA512";
-                # Use RFC-1950 ZLIB compression
-                compress-algo = "ZLIB";
-
-                # Disable weak algorithm
-                disable-cipher-algo = "3DES";
-                # Treat the specified digest algorithm as weak
-                weak-digest = "SHA1";
-
-                # The cipher algorithm for symmetric encryption for symmetric encryption with a passphrase
-                s2k-cipher-algo = "AES256";
-                # The digest algorithm used to mangle the passphrases for symmetric encryption
-                s2k-digest-algo = "SHA512";
-                # Selects how passphrases for symmetric encryption are mangled
-                s2k-mode = "3";
-                # Specify how many times the passphrases mangling for symmetric encryption is repeated
-                s2k-count = "65011712";
-              };
+        environment.sessionVariables.GNUPGHOME = gnupgHome;
+
+        systemd.services = lib.optionalAttrs (publicKeys != []) {
+          gpg-import-keys = {
+            description = "Import managed GnuPG public keys into the mutable keyring";
+
+            wantedBy = ["default.target"];
+            restartTriggers = map (key: key.source) publicKeys;
+            path = [pkgs.gnupg pkgs.coreutils];
+
+            serviceConfig = {
+              Type = "oneshot";
+              RemainAfterExit = true;
             };
-          }
-          // lib.optionalAttrs (publicKeys != []) {
-            # Immutable keyring: managed keys/trust are linked from the store.
-            ".gnupg/pubring.kbx".source = "${gpgKeyring}/pubring.kbx";
-            ".gnupg/trustdb.gpg" = {
-              type = "copy";
-              permissions = "0700";
-              source = "${gpgKeyring}/trustdb.gpg";
+
+            script = ''
+              GNUPGHOME=${lib.escapeShellArg gnupgHome}
+              export GNUPGHOME
+              mkdir -m 0700 -p "$GNUPGHOME"
+
+              ${importTrustBashFunctions}
+              ${importKeys}
+            '';
+          };
+        };
+
+        files = {
+          # This configuration is based on the tutorial below, it allows for a robust setup
+          # https://blog.eleven-labs.com/en/openpgp-almost-perfect-key-pair-part-1
+          # ~/.gnupg/gpg.conf
+          ".gnupg/gpg.conf" = {
+            generator = gpgConfGenerator;
+            value = {
+              # Get rid of the copyright notice
+              no-greeting = true;
+
+              # --- Avoid information leaked --- #
+              # Disable inclusion of the version string in ASCII armored output
+              no-emit-version = true;
+              # Do not write comment packets
+              no-comments = false;
+              # Export the smallest key possible
+              # This removes all signatures except the most recent self-signature on each user ID
+              export-options = "export-minimal";
+
+              # Display long key IDs
+              keyid-format = "0xlong";
+              # List all keys (or the specified ones) along with their fingerprints
+              with-fingerprint = true;
+
+              # Display the calculated validity of user IDs during key listings
+              list-options = "show-uid-validity";
+              verify-options = "show-uid-validity show-keyserver-urls";
+
+              # Select the strongest cipher
+              personal-cipher-preferences = "AES256";
+              # Select the strongest digest
+              personal-digest-preferences = "SHA512";
+              # This preference list is used for new keys and becomes the default for "setpref" in the edit menu
+              default-preference-list = "SHA512 SHA384 SHA256 RIPEMD160 AES256 TWOFISH BLOWFISH ZLIB BZIP2 ZIP Uncompressed";
+
+              # Use the strongest cipher algorithm
+              cipher-algo = "AES256";
+              # Use the strongest digest algorithm
+              digest-algo = "SHA512";
+              # Message digest algorithm used when signing a key
+              cert-digest-algo = "SHA512";
+              # Use RFC-1950 ZLIB compression
+              compress-algo = "ZLIB";
+
+              # Disable weak algorithm
+              disable-cipher-algo = "3DES";
+              # Treat the specified digest algorithm as weak
+              weak-digest = "SHA1";
+
+              # The cipher algorithm for symmetric encryption for symmetric encryption with a passphrase
+              s2k-cipher-algo = "AES256";
+              # The digest algorithm used to mangle the passphrases for symmetric encryption
+              s2k-digest-algo = "SHA512";
+              # Selects how passphrases for symmetric encryption are mangled
+              s2k-mode = "3";
+              # Specify how many times the passphrases mangling for symmetric encryption is repeated
+              s2k-count = "65011712";
             };
           };
+        };
       };
     };
   };