at 24.11-pre 7.4 kB view raw
1{ config, lib, pkgs, ... }: 2 3let 4 inherit (lib) mkRemovedOptionModule mkOption mkPackageOption types mkIf optionalString; 5 6 cfg = config.programs.gnupg; 7 8 agentSettingsFormat = pkgs.formats.keyValue { 9 mkKeyValue = lib.generators.mkKeyValueDefault { } " "; 10 }; 11in 12{ 13 imports = [ 14 (mkRemovedOptionModule [ "programs" "gnupg" "agent" "pinentryFlavor" ] "Use programs.gnupg.agent.pinentryPackage instead") 15 ]; 16 17 options.programs.gnupg = { 18 package = mkPackageOption pkgs "gnupg" { }; 19 20 agent.enable = mkOption { 21 type = types.bool; 22 default = false; 23 description = '' 24 Enables GnuPG agent with socket-activation for every user session. 25 ''; 26 }; 27 28 agent.enableSSHSupport = mkOption { 29 type = types.bool; 30 default = false; 31 description = '' 32 Enable SSH agent support in GnuPG agent. Also sets SSH_AUTH_SOCK 33 environment variable correctly. This will disable socket-activation 34 and thus always start a GnuPG agent per user session. 35 ''; 36 }; 37 38 agent.enableExtraSocket = mkOption { 39 type = types.bool; 40 default = false; 41 description = '' 42 Enable extra socket for GnuPG agent. 43 ''; 44 }; 45 46 agent.enableBrowserSocket = mkOption { 47 type = types.bool; 48 default = false; 49 description = '' 50 Enable browser socket for GnuPG agent. 51 ''; 52 }; 53 54 agent.pinentryPackage = mkOption { 55 type = types.nullOr types.package; 56 example = lib.literalMD "pkgs.pinentry-gnome3"; 57 default = pkgs.pinentry-curses; 58 defaultText = lib.literalMD "matching the configured desktop environment or `pkgs.pinentry-curses`"; 59 description = '' 60 Which pinentry package to use. The path to the mainProgram as defined in 61 the package's meta attriutes will be set in /etc/gnupg/gpg-agent.conf. 62 If not set by the user, it'll pick an appropriate flavor depending on the 63 system configuration (qt flavor for lxqt and plasma5, gtk2 for xfce, 64 gnome3 on all other systems with X enabled, curses otherwise). 65 ''; 66 }; 67 68 agent.settings = mkOption { 69 type = agentSettingsFormat.type; 70 default = { }; 71 example = { 72 default-cache-ttl = 600; 73 }; 74 description = '' 75 Configuration for /etc/gnupg/gpg-agent.conf. 76 See {manpage}`gpg-agent(1)` for supported options. 77 ''; 78 }; 79 80 dirmngr.enable = mkOption { 81 type = types.bool; 82 default = false; 83 description = '' 84 Enables GnuPG network certificate management daemon with socket-activation for every user session. 85 ''; 86 }; 87 }; 88 89 config = mkIf cfg.agent.enable { 90 programs.gnupg.agent.settings = mkIf (cfg.agent.pinentryPackage != null) { 91 pinentry-program = lib.getExe cfg.agent.pinentryPackage; 92 }; 93 94 environment.etc."gnupg/gpg-agent.conf".source = 95 agentSettingsFormat.generate "gpg-agent.conf" cfg.agent.settings; 96 97 # This overrides the systemd user unit shipped with the gnupg package 98 systemd.user.services.gpg-agent = { 99 unitConfig = { 100 Description = "GnuPG cryptographic agent and passphrase cache"; 101 Documentation = "man:gpg-agent(1)"; 102 Requires = [ "sockets.target" ]; 103 }; 104 serviceConfig = { 105 ExecStart = "${cfg.package}/bin/gpg-agent --supervised"; 106 ExecReload = "${cfg.package}/bin/gpgconf --reload gpg-agent"; 107 }; 108 }; 109 110 systemd.user.sockets.gpg-agent = { 111 unitConfig = { 112 Description = "GnuPG cryptographic agent and passphrase cache"; 113 Documentation = "man:gpg-agent(1)"; 114 }; 115 socketConfig = { 116 ListenStream = "%t/gnupg/S.gpg-agent"; 117 FileDescriptorName = "std"; 118 SocketMode = "0600"; 119 DirectoryMode = "0700"; 120 }; 121 wantedBy = [ "sockets.target" ]; 122 }; 123 124 systemd.user.sockets.gpg-agent-ssh = mkIf cfg.agent.enableSSHSupport { 125 unitConfig = { 126 Description = "GnuPG cryptographic agent (ssh-agent emulation)"; 127 Documentation = "man:gpg-agent(1) man:ssh-add(1) man:ssh-agent(1) man:ssh(1)"; 128 }; 129 socketConfig = { 130 ListenStream = "%t/gnupg/S.gpg-agent.ssh"; 131 FileDescriptorName = "ssh"; 132 Service = "gpg-agent.service"; 133 SocketMode = "0600"; 134 DirectoryMode = "0700"; 135 }; 136 wantedBy = [ "sockets.target" ]; 137 }; 138 139 systemd.user.sockets.gpg-agent-extra = mkIf cfg.agent.enableExtraSocket { 140 unitConfig = { 141 Description = "GnuPG cryptographic agent and passphrase cache (restricted)"; 142 Documentation = "man:gpg-agent(1)"; 143 }; 144 socketConfig = { 145 ListenStream = "%t/gnupg/S.gpg-agent.extra"; 146 FileDescriptorName = "extra"; 147 Service = "gpg-agent.service"; 148 SocketMode = "0600"; 149 DirectoryMode = "0700"; 150 }; 151 wantedBy = [ "sockets.target" ]; 152 }; 153 154 systemd.user.sockets.gpg-agent-browser = mkIf cfg.agent.enableBrowserSocket { 155 unitConfig = { 156 Description = "GnuPG cryptographic agent and passphrase cache (access for web browsers)"; 157 Documentation = "man:gpg-agent(1)"; 158 }; 159 socketConfig = { 160 ListenStream = "%t/gnupg/S.gpg-agent.browser"; 161 FileDescriptorName = "browser"; 162 Service = "gpg-agent.service"; 163 SocketMode = "0600"; 164 DirectoryMode = "0700"; 165 }; 166 wantedBy = [ "sockets.target" ]; 167 }; 168 169 systemd.user.services.dirmngr = mkIf cfg.dirmngr.enable { 170 unitConfig = { 171 Description = "GnuPG network certificate management daemon"; 172 Documentation = "man:dirmngr(8)"; 173 Requires = "dirmngr.socket"; 174 }; 175 serviceConfig = { 176 ExecStart = "${cfg.package}/bin/dirmngr --supervised"; 177 ExecReload = "${cfg.package}/bin/gpgconf --reload dirmngr"; 178 }; 179 }; 180 181 systemd.user.sockets.dirmngr = mkIf cfg.dirmngr.enable { 182 unitConfig = { 183 Description = "GnuPG network certificate management daemon"; 184 Documentation = "man:dirmngr(8)"; 185 }; 186 socketConfig = { 187 ListenStream = "%t/gnupg/S.dirmngr"; 188 SocketMode = "0600"; 189 DirectoryMode = "0700"; 190 }; 191 wantedBy = [ "sockets.target" ]; 192 }; 193 194 services.dbus.packages = mkIf (lib.elem "gnome3" (cfg.agent.pinentryPackage.flavors or [])) [ pkgs.gcr ]; 195 196 environment.systemPackages = [ cfg.package ]; 197 198 environment.interactiveShellInit = '' 199 # Bind gpg-agent to this TTY if gpg commands are used. 200 export GPG_TTY=$(tty) 201 ''; 202 203 programs.ssh.extraConfig = optionalString cfg.agent.enableSSHSupport '' 204 # The SSH agent protocol doesn't have support for changing TTYs; however we 205 # can simulate this with the `exec` feature of openssh (see ssh_config(5)) 206 # that hooks a command to the shell currently running the ssh program. 207 Match host * exec "${pkgs.runtimeShell} -c '${cfg.package}/bin/gpg-connect-agent --quiet updatestartuptty /bye >/dev/null 2>&1'" 208 ''; 209 210 environment.extraInit = mkIf cfg.agent.enableSSHSupport '' 211 if [ -z "$SSH_AUTH_SOCK" ]; then 212 export SSH_AUTH_SOCK=$(${cfg.package}/bin/gpgconf --list-dirs agent-ssh-socket) 213 fi 214 ''; 215 216 assertions = [ 217 { 218 assertion = cfg.agent.enableSSHSupport -> !config.programs.ssh.startAgent; 219 message = "You can't use ssh-agent and GnuPG agent with SSH support enabled at the same time!"; 220 } 221 ]; 222 }; 223}