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