Avoiding problems with gpg-agent when running from scripts - gpg2

1.6k views Asked by At

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?

Details

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

2

There are 2 answers

1
Adam Liss On

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
0
Rin UmU On

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-agents 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-agents with different homedirs. (For my specific case, I wanted to run multiple gpg-agent instances to have keys cached different amounts of time).