Page MenuHome GnuPG

gpgme_op_import_keys() -- unclear documentation, problematic behavior
Closed, ResolvedPublic

Description

over on https://lists.gnupg.org/pipermail/gnupg-devel/2017-June/032926.html, i wrote:


the documentation for gpgme_op_import_keys() (in "info gpgme") describes this function:

  • Function: gpgme_error_t gpgme_op_import_keys (gpgme_ctx_t CTX, gpgme_key_t *KEYS)

And it says:

The function ‘gpgme_op_import_keys’ adds the keys described by the
‘NULL’ terminated array KEYS to the key ring of the crypto engine
used by CTX.  This function is the general interface to move a key
from one crypto engine to another as long as they are compatible.
In particular it is used to actually import and make keys permanent
which have been retrieved from an external source (i.e.  using
‘GPGME_KEYLIST_MODE_EXTERN’).  (1)

Only keys of the currently selected protocol of CTX are considered
for import.  Other keys specified by the KEYS are ignored.  As of
now all considered keys must have been retrieved using the same
method, that is the used key listing mode must be identical.

Even reading the footnote (which says "(1) Thus it is a replacement for
the usual workaround of exporting and then importing a key to make an
X.509 key permanent."), it's hard to tell what any of this means. It
looks like it means i am encourage to take a key listing from one gpgme
context (e.g. a gpgme_key_t object extracted from
gpgme_get_keylist_next(ctx0) or gpgme_get_key(ctx0)) and feed it into
another (e.g..gpgme_op_import_keys(ctx1)).

But in practice, looking at src/engine-gpg.c, if i use the
gpgme_op_import_keys() form (instead of the keydata form), the backend
actually uses --recv-keys on the importing context. This doesn't work
at all if the keys are not on the public keyservers, or if the local
host is offline.

And even when keys are on the public keyservers and the local host is
online, in the case where the two contexts may have specialized
knowledge of the OpenPGP certificate (e.g. non-published certifications,
freshly-generated subkeys, etc) it has particularly strange failure
cases -- it'll result in different OpenPGP certificates held by the two
contexts.

Additionally, using the keyservers for this represents a metadata
leakage, without any warning to the user that such a thing is planned.

Finally, Its final paragraph says:

The function returns the error code ‘GPG_ERR_NO_ERROR’ if the
import was completed successfully, ‘GPG_ERR_INV_VALUE’ if KEYDATA
if CTX or KEYDATA is not a valid pointer, ‘GPG_ERR_CONFLICT’ if the
key listing mode does not match, and ‘GPG_ERR_NO_DATA’ if no keys
are considered for export.

The mention of KEYDATA seems like it might be a copy/paste issue, since
there is no KEYDATA in the function signature.


Justus confirms that this is problematic, so i'm recording this here.

Details

Version
1.9.0

Event Timeline

justus triaged this task as Normal priority.Jun 21 2017, 10:44 AM
marcus claimed this task.
marcus added a subscriber: marcus.

Most of your concerns seem to come from the "move keys" wording, which I removed. I also fixed the return values. The footnote is specific to X.509 peculiars.

Thanks for the improvements, Marcus!

There are a few more points that aren't yet addressed:

  • metadata leakage -- this calls out to the public keyservers even when one keyring is entirely local
  • it still isn't clear to me when this call should be used -- i believe that if the user wants to actually transfer a key from one keyring to another, they should *not* use op_import_keys -- they should only use op_import. is that right? If so, it'd be great to make that very clear.

You are chasing a bit of a ghost there. The operation was originally added for GPGSM to support the IMPORT --re-import command that removes the ephemeral flags from certificates that were previously imported as a side-effect of an external keylist operation. That's where the footnote comes from.

For OpenPGP, there are no ephemeral keys, and the keys that were returned by an external keylist have to be re-retrieved with another recv-keys operation.

There is no additional metadata leak, because the operation is only meaningful after an external keylisting, which already leaked basically the same data.

Nothing of this has to do with moving keys from one keyring to another. I've already removed that confusing language.

Please close.

I'm not sure i understand why i'm "chasing a ghost" -- i'm reporting the experience of a developer (me!) who tried to use gpgme, read all the docs, and was still surprised and dismayed by the metadata leakage.

i wrote some code against gpgme to handle keyring imports from sketchy sources (keys observed in some environments which may or may not be safe). This involved creating a couple different gpg contexts, one a brand-new throwaway context (indeed, an "ephemeral" OpenPGP homedir), and the other the user's actual homedir.

sketchy keys were imported into the ephemeral context and then reviewed (via op_keylist) and presented to the user for selection. they are not remote -- they're in a local ephemeral keyring.

The user selects some keys, and those selections reference a gpgme_key_t object (the natural result of op_keylist). The natural thing for me to do was to call gpgme_import_keys. I was honestly surprised as a developer to discover that my code was making network fetches.

Having gone through and re-read the gpgme source code itself, i now understand (i think) roughly what's going on, but the documentation as it stands doesn't tell me that.

Why wasn't the natural thing for you to do gpgme_op_import?

One way to prevent this mechanically would be to store an identifier for the gpgme_ctx_t object from which the gpgme_key_t object came inside the gpgme_key_t object itself, and then verifying that the keys really came from the same context. But such edge cases seem to be quite rare, and I'd hope that most developers make a tacit assumption that objects stemming from a specific context can not be repurposed in a different context ad lib.

It wasn't a natural thing to do gpgme_op_import because i already had my gpgme_key_t object, which i was using to display an index of available keys to the user.

The user reviews the index (which is bound to the gpgme_key_t) and says "i want to import this one". I don't have a gpgme_data_t object at hand that corresponds to the key, but i do have a gpgme_key_t object that gpgme just gave me.

I suppose i'm an example of a developer who didn't make the tacit assumption that you expected me to make. Would you expect me to make the same assumption about a gpgme_data_t (i.e. that i can't reuse it across contexts)? I don't know how many developers use GpgME, but i suspect i'm not the only person to not know that these expectations exist. That's why i'm asking for clearer documentation.

gpgme_data_t are first class objects with an API to create and destroy them, and some articulated rules how to use them (only one thread at a time). gpgme_key_t objects can not be created but only be returned with gpgme_op_keylist_next.

Maybe you can suggest a better wording for gpgme_op_import_keys that makes things clearer.