Reputation: 15907
I'm trying to use gpg
to --clearsign
a file (for debian packaging purposes) from a script.
I have an exported password-less private-key.gpg
file and want to:
gpg --clearsign -o output input
I don't want to mess with the current user's ~/.gnupg
or /run/user/$(id -u)/gnupg
because they have nothing to do with my script. Also, the script could be running in multiple instances simultaneously and I don't want them interfering with one another.
I thought that would be easy. Setup $GNUPGHOME
to a temp dir and be done with it. But I cannot figure out how to get gpg
to run in a script without messing with the user's standard configuration at all. It seems gpg
has gone to great lengths to make it impossible to avoid the gpg-agent
and gpg-agent
insists on using global/hard-coded paths.
Can I keep everything under $GNUPGHOME
? Or how do I safely use gpg
from a shell script without influencing the user's config or use of gpg
or other instances of my script?
Reading the gpg docs I see that:
--use-agent
--no-use-agent
This is dummy option. gpg always requires the agent.
And gpg-agent docs say:
--use-standard-socket
--no-use-standard-socket
--use-standard-socket-p
Since GnuPG 2.1 the standard socket is always used.
These options have no more effect. The command gpg-agent
--use-standard-socket-p will thus always return success.
This "standard socket" is presumably in /run/user/$(id -u)/gnupg
- so it seems I can't avoid gpg messing with the user's "normal" use of gpg.
Versions: gpg 2.1.18 on Debian 9 / stretch / stable
Upvotes: 11
Views: 2218
Reputation: 11
After multiple hours of searching the internet for some option to enable running multiple instances of gpg-agent
with different gnupg homes I tried just restricting the access to the global socket location and it worked. It fell back to placing the socket files in the gnupg home directory. I used bwrap
to do that. Here's the full command that worked: bwrap --dev-bind / / --tmpfs /run/user/$(id -u)/gnupg gpg-agent ...
. Since your question is about scripts in general, you probably can't rely on bwrap
being installed, so the next best thing is a shim that prevents gpg-agent
from using the user's xdg runtime directory for its sockets.
After looking through the output of strace, the switch of location when running under bwrap
to gnupg home seems to happen after a stat
on /run/user/${UID}/gnupg
is issued. Looking through the gpg-agent.c
code this seems to do that check:
/* Check that it is a directory, owned by the user, and only the
* user has permissions to use it. */
if (!S_ISDIR(sb.st_mode)
|| sb.st_uid != getuid ()
|| (sb.st_mode & (S_IRWXG|S_IRWXO)))
{
*r_info |= 4; /* Bad permissions or not a directory. */
if (!skip_checks)
goto leave;
}
Using this we can just tell gpg-agent
that the /run/user/${UID}/gnupg
exists, but is not a directory, so it will fail the first of these checks.
Here's the code for a shim that does just that (could be better, but it works):
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <dlfcn.h>
#include <stdio.h>
#define STR_MAX 4096
#define MIN(a, b) (a < b ? a : b)
#define MAX(a, b) (a > b ? a : b)
// Set up checking stuff
#define MAX_UID_LEN 11
#define PREFIX_1 "/run/user"
#define PREFIX_2 "/var/run/user"
#define TEST_LEN_1 (sizeof(PREFIX_1) - 1 + 1 + MAX_UID_LEN + sizeof("/gnupg") - 1)
#define TEST_LEN_2 (sizeof(PREFIX_2) - 1 + 1 + MAX_UID_LEN + sizeof("/gnupg") - 1)
#define MAX_TEST_LEN MAX(TEST_LEN_1, TEST_LEN_2)
// Override stat function
int stat(const char *restrict pathname, struct stat *restrict statbuf) {
int (*original_stat)(const char *restrict, struct stat *restrict) = dlsym(RTLD_NEXT, "stat");
// Call original stat function
int retval = original_stat(pathname, statbuf);
if (retval == 0) {
// Check if a path we want to modify
size_t pathlen = strnlen(pathname, STR_MAX);
char path_check[MAX_TEST_LEN + 1];
snprintf(path_check, MAX_TEST_LEN + 1, "%s/%u/gnupg", PREFIX_1, getuid());
if (strncmp(pathname, path_check, MIN(MAX_TEST_LEN, pathlen)) == 0) {
// Report a regular file with perms: rwxrwxrwx
statbuf->st_mode = S_IFREG|0777;
}
snprintf(path_check, MAX_TEST_LEN + 1, "%s/%u/gnupg", PREFIX_2, getuid());
if (strncmp(pathname, path_check, MIN(MAX_TEST_LEN, pathlen)) == 0) {
// Report a regular file with perms: rwxrwxrwx
statbuf->st_mode = S_IFREG|0777;
}
}
return retval;
}
You can compile it with: clang -Wall -O2 -fpic -shared -ldl -o gpg-shim.so gpg-shim.c
and then add it to LD_PRELOAD
and it should then allow you to run multiple gpg-agent
s as long as they have different gnupg homes.
I know this answer is really late, but I hope it can help some people that were, like me, looking for a way to run multiple gpg-agent
s with different homedirs. (For my specific case, I wanted to run multiple gpg-agent
instances to have keys cached different amounts of time).
Upvotes: 1
Reputation: 48290
If you can't stop gpg from creating files, would it help to give gpg a place to put them that's unique to the current process?
# Create a temporary directory for gpg.
dir="$(mktemp -d)"
# Remove the directory and its contents when the script exits.
trap '[[ ! -d "${dir}" ]] || rm -r "${dir}"' EXIT
# Put your private-key.gpg in the temporary directory.
$(your command here)
# Tell gpg to use the temporary directory.
gpg --homedir "${dir}" --clearsign -o output input
Upvotes: 1